Compare commits

..

241 Commits

Author SHA1 Message Date
semantic-release-bot
b2fcd5a846 chore: Release v5.24.0-dev.5 [skip ci]
# [5.24.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.4...v5.24.0-dev.5) (2025-05-17)

### Bug Fixes

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

### Features

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

### Features

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

### Bug Fixes

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

### Features

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Features

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Features

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Features

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

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

### Bug Fixes

* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([8d0bca3](8d0bca3b03))
2025-03-06 10:36:52 +00:00
oSumAtrIX
8d0bca3b03 fix(Boost for reddit - Client spoof): Use a different user agent to combat Reddit's API issues 2025-03-06 11:33:06 +01:00
semantic-release-bot
c162d65d5b chore: Release v5.14.0-dev.2 [skip ci]
# [5.14.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.1...v5.14.0-dev.2) (2025-03-06)

### Bug Fixes

* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([67dcd09](67dcd091c4))
2025-03-06 09:30:51 +00:00
ILoveOpenSourceApplications
67dcd091c4 fix(YouTube - Hide ads): Hide new type of buttoned ad (#4528) 2025-03-06 11:27:37 +02:00
github-actions[bot]
ac5ce2d67f chore: Sync translations (#4553) 2025-03-06 11:26:20 +02:00
LisoUseInAIKyrios
4b78d056fd ci: Pull Crowdin strings less often
Crowdin is starting to give errors and pulling less often may help.
2025-03-06 11:25:22 +02:00
410 changed files with 14780 additions and 9335 deletions

View File

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

View File

@@ -1,3 +1,796 @@
# [5.24.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.4...v5.24.0-dev.5) (2025-05-17)
### Bug Fixes
* **Spotify - Fix third party launchers widgets:** Add missing compatibility annotation ([0493f80](https://github.com/ReVanced/revanced-patches/commit/0493f8035b26b90c5f8e42be2e2a5ce73d8685a5))
### Features
* **YouTube - Settings:** Add ability to search in settings ([#4881](https://github.com/ReVanced/revanced-patches/issues/4881)) ([aca8b20](https://github.com/ReVanced/revanced-patches/commit/aca8b207c15f254bcc9ad94bc7dfb895f21d4058))
# [5.24.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.3...v5.24.0-dev.4) (2025-05-16)
### Features
* **Spotify:** Add `Fix third party launchers widgets` patch ([#4893](https://github.com/ReVanced/revanced-patches/issues/4893)) ([23bfdc9](https://github.com/ReVanced/revanced-patches/commit/23bfdc98fbbcc8ecf0ffbf8704f58dd2272e4af2))
# [5.24.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.2...v5.24.0-dev.3) (2025-05-14)
### Bug Fixes
* **YouTube - Hide layout components:** Fix `Hide video recommendation labels` ([#4956](https://github.com/ReVanced/revanced-patches/issues/4956)) ([ae05ac3](https://github.com/ReVanced/revanced-patches/commit/ae05ac38151ebd3197953af97ca0dd847a04cc2d))
# [5.24.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.1...v5.24.0-dev.2) (2025-05-14)
### Features
* **GmsCore support:** Open vendor specific DontKillMyApp if available ([#4952](https://github.com/ReVanced/revanced-patches/issues/4952)) ([b89927a](https://github.com/ReVanced/revanced-patches/commit/b89927a10e3b909a3c37fbb75c16a7abbce44560))
* **YouTube - Hide player components:** Hide related video overlay in fullscreen ([#4938](https://github.com/ReVanced/revanced-patches/issues/4938)) ([ac9be97](https://github.com/ReVanced/revanced-patches/commit/ac9be9760c9965e54df196b227a310d64ead4bf5))
# [5.24.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.23.0...v5.24.0-dev.1) (2025-05-12)
### Features
* **NU.nl:** Support version `11.3.0` ([#4925](https://github.com/ReVanced/revanced-patches/issues/4925)) ([bedde60](https://github.com/ReVanced/revanced-patches/commit/bedde60fc1a52b0fd491174b3b5b887435eb621a))
# [5.23.0](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0) (2025-05-10)
### Bug Fixes
* Correct incorrect fingerprint ([c3bab89](https://github.com/ReVanced/revanced-patches/commit/c3bab89fc4189e38c10eee0caa36289de7e29dfa))
* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([49ca329](https://github.com/ReVanced/revanced-patches/commit/49ca3290a726cdba7bc9b62ffcd8d46e6f04778e))
* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([00aa200](https://github.com/ReVanced/revanced-patches/commit/00aa2000ba2eef15a0dd827c2bd84c2e85c412e0))
* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([7b43986](https://github.com/ReVanced/revanced-patches/commit/7b43986871a68e5cb43331d2fb2fdb9ef67438ad))
* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([bd53955](https://github.com/ReVanced/revanced-patches/commit/bd53955df738bb7b819eb91a3e776e9d2ca5c74a))
### Features
* **Lightroom:** Constrain patches to last working version ([efef03b](https://github.com/ReVanced/revanced-patches/commit/efef03b80da21552d0d8be6913faba64e4fb5ed1))
* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([0cf7a4c](https://github.com/ReVanced/revanced-patches/commit/0cf7a4c6be615ed0a52a6bacf87592f5f43ff575))
* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([bb672c4](https://github.com/ReVanced/revanced-patches/commit/bb672c4674ddc201b8b2648c3906cfc31ef43f10))
* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([2e3511d](https://github.com/ReVanced/revanced-patches/commit/2e3511d03c8198bbdb9336888df038a33fb3ab8c))
# [5.23.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.6...v5.23.0-dev.7) (2025-05-06)
### Bug Fixes
* Fix incorrect fingerprints ([#4917](https://github.com/ReVanced/revanced-patches/issues/4917)) ([49ca329](https://github.com/ReVanced/revanced-patches/commit/49ca3290a726cdba7bc9b62ffcd8d46e6f04778e))
# [5.23.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.5...v5.23.0-dev.6) (2025-05-06)
### Bug Fixes
* Correct incorrect fingerprint ([c3bab89](https://github.com/ReVanced/revanced-patches/commit/c3bab89fc4189e38c10eee0caa36289de7e29dfa))
# [5.23.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.4...v5.23.0-dev.5) (2025-05-06)
### Bug Fixes
* **Spotify - Unlock Spotify Premium:** Remove pop up premium ads ([#4842](https://github.com/ReVanced/revanced-patches/issues/4842)) ([00aa200](https://github.com/ReVanced/revanced-patches/commit/00aa2000ba2eef15a0dd827c2bd84c2e85c412e0))
### Features
* **Pandora:** Add `Disable audio ads` and `Unlimited skips` patch ([#4841](https://github.com/ReVanced/revanced-patches/issues/4841)) ([0cf7a4c](https://github.com/ReVanced/revanced-patches/commit/0cf7a4c6be615ed0a52a6bacf87592f5f43ff575))
* **Prime Video:** Add `Skip ads` patch ([#4824](https://github.com/ReVanced/revanced-patches/issues/4824)) ([bb672c4](https://github.com/ReVanced/revanced-patches/commit/bb672c4674ddc201b8b2648c3906cfc31ef43f10))
# [5.23.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.3...v5.23.0-dev.4) (2025-05-06)
### Features
* **Spotify:** Add `Sanitize sharing links` patch ([#4829](https://github.com/ReVanced/revanced-patches/issues/4829)) ([2e3511d](https://github.com/ReVanced/revanced-patches/commit/2e3511d03c8198bbdb9336888df038a33fb3ab8c))
# [5.23.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.2...v5.23.0-dev.3) (2025-05-05)
### Bug Fixes
* **YouTube:** Simplify litho filtering patch ([#4910](https://github.com/ReVanced/revanced-patches/issues/4910)) ([bd53955](https://github.com/ReVanced/revanced-patches/commit/bd53955df738bb7b819eb91a3e776e9d2ca5c74a))
# [5.23.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.23.0-dev.1...v5.23.0-dev.2) (2025-05-04)
### Bug Fixes
* **YouTube:** Improve litho filtering performance ([#4904](https://github.com/ReVanced/revanced-patches/issues/4904)) ([7b43986](https://github.com/ReVanced/revanced-patches/commit/7b43986871a68e5cb43331d2fb2fdb9ef67438ad))
# [5.23.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.22.0...v5.23.0-dev.1) (2025-05-02)
### Features
* **Lightroom:** Constrain patches to last working version ([efef03b](https://github.com/ReVanced/revanced-patches/commit/efef03b80da21552d0d8be6913faba64e4fb5ed1))
# [5.22.0](https://github.com/ReVanced/revanced-patches/compare/v5.21.0...v5.22.0) (2025-05-01)
### Bug Fixes
* **TikTok - Feed filter:** Hide ads in following feed ([#4844](https://github.com/ReVanced/revanced-patches/issues/4844)) ([c255ac1](https://github.com/ReVanced/revanced-patches/commit/c255ac18e0b2dcf917bd0559876be5a2a81023db))
* **YouTube - Hide layout components:** Hide new type of community posts ([#4888](https://github.com/ReVanced/revanced-patches/issues/4888)) ([f0c9c35](https://github.com/ReVanced/revanced-patches/commit/f0c9c35778ab43a99149ee5ad0ccfd8aeb09f638))
* **YouTube - Hide Shorts components:** Hide action buttons A/B button layout ([#4889](https://github.com/ReVanced/revanced-patches/issues/4889)) ([9dcd3d3](https://github.com/ReVanced/revanced-patches/commit/9dcd3d35dddf019547ab6ce431bac7a5a8a4c291))
* **YouTube - Shorts autoplay:** Fix autoplay with YT 20.12 ([06b35b2](https://github.com/ReVanced/revanced-patches/commit/06b35b2a7d7371915881e8f430c32ce15fa224de))
* **YouTube - Spoof app version:** Do not hide spoof version in general settings menu ([#4861](https://github.com/ReVanced/revanced-patches/issues/4861)) ([f459c3c](https://github.com/ReVanced/revanced-patches/commit/f459c3c7fae3a1b8addf3354488dcef9f95255cc))
### Features
* **TikTok - Feed Filter:** Remove TikTok Shop from feed. ([#4851](https://github.com/ReVanced/revanced-patches/issues/4851)) ([f198bec](https://github.com/ReVanced/revanced-patches/commit/f198bece653e3e1adf083129dedb77c1d1a633d7))
* **YouTube - GmsCore support:** Show troubleshooting in app text if the user recently changed their account details ([#4879](https://github.com/ReVanced/revanced-patches/issues/4879)) ([ab4bdc8](https://github.com/ReVanced/revanced-patches/commit/ab4bdc8a2519cee15f79bf95d89e7ea56ea464ee))
# [5.22.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.3...v5.22.0-dev.4) (2025-04-30)
### Bug Fixes
* **YouTube - Hide layout components:** Hide new type of community posts ([#4888](https://github.com/ReVanced/revanced-patches/issues/4888)) ([f0c9c35](https://github.com/ReVanced/revanced-patches/commit/f0c9c35778ab43a99149ee5ad0ccfd8aeb09f638))
* **YouTube - Hide Shorts components:** Hide action buttons A/B button layout ([#4889](https://github.com/ReVanced/revanced-patches/issues/4889)) ([9dcd3d3](https://github.com/ReVanced/revanced-patches/commit/9dcd3d35dddf019547ab6ce431bac7a5a8a4c291))
# [5.22.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.2...v5.22.0-dev.3) (2025-04-29)
### Features
* **YouTube - GmsCore support:** Show troubleshooting in app text if the user recently changed their account details ([#4879](https://github.com/ReVanced/revanced-patches/issues/4879)) ([ab4bdc8](https://github.com/ReVanced/revanced-patches/commit/ab4bdc8a2519cee15f79bf95d89e7ea56ea464ee))
# [5.22.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.22.0-dev.1...v5.22.0-dev.2) (2025-04-27)
### Bug Fixes
* **YouTube - Shorts autoplay:** Fix autoplay with YT 20.12 ([06b35b2](https://github.com/ReVanced/revanced-patches/commit/06b35b2a7d7371915881e8f430c32ce15fa224de))
# [5.22.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.21.0...v5.22.0-dev.1) (2025-04-26)
### Bug Fixes
* **TikTok - Feed filter:** Hide ads in following feed ([#4844](https://github.com/ReVanced/revanced-patches/issues/4844)) ([c255ac1](https://github.com/ReVanced/revanced-patches/commit/c255ac18e0b2dcf917bd0559876be5a2a81023db))
* **YouTube - Spoof app version:** Do not hide spoof version in general settings menu ([#4861](https://github.com/ReVanced/revanced-patches/issues/4861)) ([f459c3c](https://github.com/ReVanced/revanced-patches/commit/f459c3c7fae3a1b8addf3354488dcef9f95255cc))
### Features
* **TikTok - Feed Filter:** Remove TikTok Shop from feed. ([#4851](https://github.com/ReVanced/revanced-patches/issues/4851)) ([f198bec](https://github.com/ReVanced/revanced-patches/commit/f198bece653e3e1adf083129dedb77c1d1a633d7))
# [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)
### Bug Fixes
* **YouTube - Seekbar:** Correctly hide the feed seekbar with target 20.07 ([ddc6e4c](https://github.com/ReVanced/revanced-patches/commit/ddc6e4c34fe35fa34bd859bf34e25645a23dbdc9))
# [5.19.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.2...v5.19.0-dev.3) (2025-04-02)
### Features
* **Proton Mail:** Add `Remove 'Sent from' signature` patch ([#4514](https://github.com/ReVanced/revanced-patches/issues/4514)) ([34c14c9](https://github.com/ReVanced/revanced-patches/commit/34c14c9b443092824d035afd77adb678c6f89e3e))
# [5.19.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.19.0-dev.1...v5.19.0-dev.2) (2025-04-02)
### Features
* **YouTube - Settings:** Add icons to the ReVanced settings ([#4496](https://github.com/ReVanced/revanced-patches/issues/4496)) ([d0c85f0](https://github.com/ReVanced/revanced-patches/commit/d0c85f044083d720c63a8ea4ff15d42eefeb9db7))
# [5.19.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.18.1-dev.2...v5.19.0-dev.1) (2025-04-01)
### Bug Fixes
* **Twitter - Hide recommended users:** Make hiding work again by filtering for new entryId prefix ([#4456](https://github.com/ReVanced/revanced-patches/issues/4456)) ([ff846b0](https://github.com/ReVanced/revanced-patches/commit/ff846b0b7ef5060caaffedb08c1f901172f5b2d1))
### Features
* **Angulus:** Add `Hide ads` patch ([#4604](https://github.com/ReVanced/revanced-patches/issues/4604)) ([87c86b5](https://github.com/ReVanced/revanced-patches/commit/87c86b53a91b0054ac892a3f02bbe7bf83bbf813))
* **Photomath:** Support latest version ([#4672](https://github.com/ReVanced/revanced-patches/issues/4672)) ([8e16483](https://github.com/ReVanced/revanced-patches/commit/8e1648322948151e4565fb0d86e0f37d0a02d73f))
## [5.18.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.18.1-dev.1...v5.18.1-dev.2) (2025-04-01)
### Bug Fixes
* **YouTube:** Combine multiple seekbar patches into a single patch ([#4705](https://github.com/ReVanced/revanced-patches/issues/4705)) ([503b7eb](https://github.com/ReVanced/revanced-patches/commit/503b7eb8d413ef7f248394f128f3b2a6f3192ba6))
## [5.18.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.18.0...v5.18.1-dev.1) (2025-03-31)
### Bug Fixes
* **YouTube - Remove background playback restrictions:** Do not show media controls when playing Shorts from the feed ([2ed675c](https://github.com/ReVanced/revanced-patches/commit/2ed675cdd058fb5876381a9d30dee5263f6b2e26))
# [5.18.0](https://github.com/ReVanced/revanced-patches/compare/v5.17.0...v5.18.0) (2025-03-28)
### Bug Fixes
* **Spotify:** Ignore optional attributes if not present ([#4688](https://github.com/ReVanced/revanced-patches/issues/4688)) ([84f5854](https://github.com/ReVanced/revanced-patches/commit/84f585492e4be3604c6c7680ffb3bebcea5a675f))
### Features
* **YouTube:** Support version `20.07.39` ([#4677](https://github.com/ReVanced/revanced-patches/issues/4677)) ([c1379f6](https://github.com/ReVanced/revanced-patches/commit/c1379f6e520c683d2c9d6a490a69ca542168b3b3))
# [5.18.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.18.0-dev.1...v5.18.0-dev.2) (2025-03-28)
### Bug Fixes
* **Spotify:** Ignore optional attributes if not present ([#4688](https://github.com/ReVanced/revanced-patches/issues/4688)) ([84f5854](https://github.com/ReVanced/revanced-patches/commit/84f585492e4be3604c6c7680ffb3bebcea5a675f))
# [5.18.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.17.0...v5.18.0-dev.1) (2025-03-28)
### Features
* **YouTube:** Support version `20.07.39` ([#4677](https://github.com/ReVanced/revanced-patches/issues/4677)) ([c1379f6](https://github.com/ReVanced/revanced-patches/commit/c1379f6e520c683d2c9d6a490a69ca542168b3b3))
# [5.17.0](https://github.com/ReVanced/revanced-patches/compare/v5.16.1...v5.17.0) (2025-03-28)
### Bug Fixes
* **Facebook - Hide 'Sponsored Stories':** Constrain patch to latest compatible version ([#4657](https://github.com/ReVanced/revanced-patches/issues/4657)) ([46bd1c8](https://github.com/ReVanced/revanced-patches/commit/46bd1c829acd5f83600025e0ceb7d482ae80be69))
* **Spotify - Unlock Premium:** Override additional attributes ([#4651](https://github.com/ReVanced/revanced-patches/issues/4651)) ([568b40d](https://github.com/ReVanced/revanced-patches/commit/568b40da9692eae9039bbb3cec513a61ca627c24))
* **Spotify - Unlock Premium:** Use correct patch description convention ([a486522](https://github.com/ReVanced/revanced-patches/commit/a4865228f8481d2efc8fbf4e90902a03289d9a3f))
* **X / Twitter:** Constrain patches to latest compatible versions ([#4683](https://github.com/ReVanced/revanced-patches/issues/4683)) ([f579728](https://github.com/ReVanced/revanced-patches/commit/f5797289f45186052537982c7f5db6f2b0769aee))
* **YouTube - Navigation buttons:** Add user dialog message to 'Disable translucent status bar' ([a4a0e68](https://github.com/ReVanced/revanced-patches/commit/a4a0e6869e23d15ee09262460f4e290c90629eeb))
### Features
* **Spotify - Unlock Premium:** Disable the "Spotify Premium" upsell experiment in context menus ([9a10ee4](https://github.com/ReVanced/revanced-patches/commit/9a10ee4d22fb53da2012a182e038749d3ad72377))
# [5.17.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.3...v5.17.0-dev.4) (2025-03-28)
### Bug Fixes
* **X / Twitter:** Constrain patches to latest compatible versions ([#4683](https://github.com/ReVanced/revanced-patches/issues/4683)) ([f579728](https://github.com/ReVanced/revanced-patches/commit/f5797289f45186052537982c7f5db6f2b0769aee))
# [5.17.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.2...v5.17.0-dev.3) (2025-03-28)
### Bug Fixes
* **Spotify - Unlock Premium:** Override additional attributes ([#4651](https://github.com/ReVanced/revanced-patches/issues/4651)) ([568b40d](https://github.com/ReVanced/revanced-patches/commit/568b40da9692eae9039bbb3cec513a61ca627c24))
# [5.17.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.17.0-dev.1...v5.17.0-dev.2) (2025-03-27)
### Bug Fixes
* **YouTube - Navigation buttons:** Add user dialog message to 'Disable translucent status bar' ([a4a0e68](https://github.com/ReVanced/revanced-patches/commit/a4a0e6869e23d15ee09262460f4e290c90629eeb))
# [5.17.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.2-dev.1...v5.17.0-dev.1) (2025-03-27)
### Bug Fixes
* **Spotify - Unlock Premium:** Use correct patch description convention ([a486522](https://github.com/ReVanced/revanced-patches/commit/a4865228f8481d2efc8fbf4e90902a03289d9a3f))
### Features
* **Spotify - Unlock Premium:** Disable the "Spotify Premium" upsell experiment in context menus ([9a10ee4](https://github.com/ReVanced/revanced-patches/commit/9a10ee4d22fb53da2012a182e038749d3ad72377))
## [5.16.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.1...v5.16.2-dev.1) (2025-03-26)
### Bug Fixes
* **Facebook - Hide 'Sponsored Stories':** Constrain patch to latest compatible version ([#4657](https://github.com/ReVanced/revanced-patches/issues/4657)) ([46bd1c8](https://github.com/ReVanced/revanced-patches/commit/46bd1c829acd5f83600025e0ceb7d482ae80be69))
## [5.16.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.0...v5.16.1) (2025-03-26)
### Bug Fixes
* **Spotify - Unlock Premium:** Override streaming attribute attempting to fix streaming issues ([06be36c](https://github.com/ReVanced/revanced-patches/commit/06be36cddf3430b4179dff696b3d15718cd6963b))
## [5.16.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.0...v5.16.1-dev.1) (2025-03-26)
### Bug Fixes
* **Spotify - Unlock Premium:** Override streaming attribute attempting to fix streaming issues ([06be36c](https://github.com/ReVanced/revanced-patches/commit/06be36cddf3430b4179dff696b3d15718cd6963b))
# [5.16.0](https://github.com/ReVanced/revanced-patches/compare/v5.15.0...v5.16.0) (2025-03-26)
### Bug Fixes
* **YouTube - Settings:** System navigation bar is located above the settings ui on Android 15+ ([f7497be](https://github.com/ReVanced/revanced-patches/commit/f7497be2c5e4abcde6eb55b84955124a28f55cae))
### Features
* **Spotify:** Add `Unlock premium` patch ([#4644](https://github.com/ReVanced/revanced-patches/issues/4644)) ([f048c50](https://github.com/ReVanced/revanced-patches/commit/f048c50e56fc1f5a5c607860be4206ef83b528fe))
* **YouTube - Comments:** Add `Hide AI Comments summary` ([#4634](https://github.com/ReVanced/revanced-patches/issues/4634)) ([e9b7f26](https://github.com/ReVanced/revanced-patches/commit/e9b7f263f739bd130f6ea79913851a52355977c5))
* **YouTube - Video description:** Add `Hide AI-generated video summary` ([#4636](https://github.com/ReVanced/revanced-patches/issues/4636)) ([521fd48](https://github.com/ReVanced/revanced-patches/commit/521fd48602432ab436d8711c19d7130b2b05af12))
# [5.16.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.16.0-dev.1...v5.16.0-dev.2) (2025-03-26)
### Features
* **Spotify:** Add `Unlock premium` patch ([#4644](https://github.com/ReVanced/revanced-patches/issues/4644)) ([f048c50](https://github.com/ReVanced/revanced-patches/commit/f048c50e56fc1f5a5c607860be4206ef83b528fe))
# [5.16.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.15.0...v5.16.0-dev.1) (2025-03-24)
### Bug Fixes
* **YouTube - Settings:** System navigation bar is located above the settings ui on Android 15+ ([f7497be](https://github.com/ReVanced/revanced-patches/commit/f7497be2c5e4abcde6eb55b84955124a28f55cae))
### Features
* **YouTube - Comments:** Add `Hide AI Comments summary` ([#4634](https://github.com/ReVanced/revanced-patches/issues/4634)) ([e9b7f26](https://github.com/ReVanced/revanced-patches/commit/e9b7f263f739bd130f6ea79913851a52355977c5))
* **YouTube - Video description:** Add `Hide AI-generated video summary` ([#4636](https://github.com/ReVanced/revanced-patches/issues/4636)) ([521fd48](https://github.com/ReVanced/revanced-patches/commit/521fd48602432ab436d8711c19d7130b2b05af12))
# [5.15.0](https://github.com/ReVanced/revanced-patches/compare/v5.14.0...v5.15.0) (2025-03-21)
### Bug Fixes
* **YouTube - Spoof app version:** Change oldest spoof target to 19.01.34 ([5012439](https://github.com/ReVanced/revanced-patches/commit/5012439a8e53b2a4ab5e85c47976e1ab28a51208))
* **YouTube - Spoof app version:** Remove broken spoof targets that YouTube no longer supports ([#4610](https://github.com/ReVanced/revanced-patches/issues/4610)) ([883fbe7](https://github.com/ReVanced/revanced-patches/commit/883fbe71233c57cb1241e57c122b43f40722acc7))
* **YouTube:** Do not show restart prompt more than once if setting change is canceled ([49797fe](https://github.com/ReVanced/revanced-patches/commit/49797fe8d0c4a0981ef621a31356c4315ae3777b))
### Features
* **YouTube - SponsorBlock:** Add opacity setting to category segment colors ([#4582](https://github.com/ReVanced/revanced-patches/issues/4582)) ([6e8ffba](https://github.com/ReVanced/revanced-patches/commit/6e8ffbade9e03658f725622631e44dabf2995861))
# [5.15.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.3...v5.15.0-dev.4) (2025-03-21)
### Bug Fixes
* **YouTube - Spoof app version:** Change oldest spoof target to 19.01.34 ([5012439](https://github.com/ReVanced/revanced-patches/commit/5012439a8e53b2a4ab5e85c47976e1ab28a51208))
# [5.15.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.2...v5.15.0-dev.3) (2025-03-20)
### Bug Fixes
* **YouTube:** Do not show restart prompt more than once if setting change is canceled ([49797fe](https://github.com/ReVanced/revanced-patches/commit/49797fe8d0c4a0981ef621a31356c4315ae3777b))
# [5.15.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.1...v5.15.0-dev.2) (2025-03-19)
### Bug Fixes
* **YouTube - Spoof app version:** Remove broken spoof targets that YouTube no longer supports ([#4610](https://github.com/ReVanced/revanced-patches/issues/4610)) ([883fbe7](https://github.com/ReVanced/revanced-patches/commit/883fbe71233c57cb1241e57c122b43f40722acc7))
# [5.15.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.14.0...v5.15.0-dev.1) (2025-03-19)
### Features
* **YouTube - SponsorBlock:** Add opacity setting to category segment colors ([#4582](https://github.com/ReVanced/revanced-patches/issues/4582)) ([6e8ffba](https://github.com/ReVanced/revanced-patches/commit/6e8ffbade9e03658f725622631e44dabf2995861))
# [5.14.0](https://github.com/ReVanced/revanced-patches/compare/v5.13.0...v5.14.0) (2025-03-09)
### Bug Fixes
* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([5d3c817](https://github.com/ReVanced/revanced-patches/commit/5d3c8175b34a3f6ae2732b25db0851773a8c000d))
* **YouTube - Change form factor:** Restore Automotive form factor watch history menu, channel pages, and community posts ([#4541](https://github.com/ReVanced/revanced-patches/issues/4541)) ([aa5c001](https://github.com/ReVanced/revanced-patches/commit/aa5c001968446e5270c756256724e917009612cd))
* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([4387a7b](https://github.com/ReVanced/revanced-patches/commit/4387a7b131f49729e902e008bb4cec073635c040))
* **YouTube - Hide layout components:** Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled ([62a6164](https://github.com/ReVanced/revanced-patches/commit/62a6164b88b64200b517a5ba6b800d8214dbbad8))
* **YouTube - Theme:** Resolve dark mode startup crash with Android 9.0 ([741c2d5](https://github.com/ReVanced/revanced-patches/commit/741c2d59406f5d602554bb3a3c0b8982f42848b4))
* **YouTube:** Change language settings menu to use native language names ([#4568](https://github.com/ReVanced/revanced-patches/issues/4568)) ([6f3f8fd](https://github.com/ReVanced/revanced-patches/commit/6f3f8fdce05501e4fa4423c2170a916fbea3b199))
* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee67b76](https://github.com/ReVanced/revanced-patches/commit/ee67b763d5c5947a5b1ef4420b1efa820ed6af83))
### Features
* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([cf9f959](https://github.com/ReVanced/revanced-patches/commit/cf9f959923076c10a7f0a29f6ba277f5a055ec07))
* **Spotify:** Add `Spoof signature` patch ([#4576](https://github.com/ReVanced/revanced-patches/issues/4576)) ([3646c70](https://github.com/ReVanced/revanced-patches/commit/3646c70556b67a6b7ecf9b86869ebf03c3611333))
* **YouTube - Remember video quality:** Add separate Shorts default quality settings ([#4543](https://github.com/ReVanced/revanced-patches/issues/4543)) ([88142ab](https://github.com/ReVanced/revanced-patches/commit/88142ab464192b564b1b8d56a6b45663f77f5e00))
# [5.14.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.8...v5.14.0-dev.9) (2025-03-09)
### Features
* **Spotify:** Add `Spoof signature` patch ([#4576](https://github.com/ReVanced/revanced-patches/issues/4576)) ([3646c70](https://github.com/ReVanced/revanced-patches/commit/3646c70556b67a6b7ecf9b86869ebf03c3611333))
# [5.14.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.7...v5.14.0-dev.8) (2025-03-09)
### Bug Fixes
* **YouTube - Theme:** Resolve dark mode startup crash with Android 9.0 ([741c2d5](https://github.com/ReVanced/revanced-patches/commit/741c2d59406f5d602554bb3a3c0b8982f42848b4))
# [5.14.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.6...v5.14.0-dev.7) (2025-03-08)
### Bug Fixes
* **YouTube:** Change language settings menu to use native language names ([#4568](https://github.com/ReVanced/revanced-patches/issues/4568)) ([6f3f8fd](https://github.com/ReVanced/revanced-patches/commit/6f3f8fdce05501e4fa4423c2170a916fbea3b199))
# [5.14.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.5...v5.14.0-dev.6) (2025-03-07)
### Bug Fixes
* **YouTube - Hide layout components:** Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled ([62a6164](https://github.com/ReVanced/revanced-patches/commit/62a6164b88b64200b517a5ba6b800d8214dbbad8))
# [5.14.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.4...v5.14.0-dev.5) (2025-03-06)
### Features
* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([cf9f959](https://github.com/ReVanced/revanced-patches/commit/cf9f959923076c10a7f0a29f6ba277f5a055ec07))
# [5.14.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.3...v5.14.0-dev.4) (2025-03-06)
### Bug Fixes
* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee67b76](https://github.com/ReVanced/revanced-patches/commit/ee67b763d5c5947a5b1ef4420b1efa820ed6af83))
# [5.14.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.2...v5.14.0-dev.3) (2025-03-06)
### Bug Fixes
* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([5d3c817](https://github.com/ReVanced/revanced-patches/commit/5d3c8175b34a3f6ae2732b25db0851773a8c000d))
# [5.14.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.1...v5.14.0-dev.2) (2025-03-06)
### Bug Fixes
* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([4387a7b](https://github.com/ReVanced/revanced-patches/commit/4387a7b131f49729e902e008bb4cec073635c040))
# [5.14.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.1-dev.1...v5.14.0-dev.1) (2025-03-06) # [5.14.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.1-dev.1...v5.14.0-dev.1) (2025-03-06)

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.shared; package app.revanced.extension.shared;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
@@ -15,14 +16,18 @@ import android.os.Build;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import java.net.HttpURLConnection;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.Locale;
/** import app.revanced.extension.shared.requests.Requester;
* @noinspection unused import app.revanced.extension.shared.requests.Route;
*/
@SuppressWarnings("unused")
public class GmsCoreSupport { public class GmsCoreSupport {
private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube"; private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube";
private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music"; private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music";
@@ -31,10 +36,24 @@ public class GmsCoreSupport {
= getGmsCoreVendorGroupId() + ".android.gms"; = getGmsCoreVendorGroupId() + ".android.gms";
private static final Uri GMS_CORE_PROVIDER private static final Uri GMS_CORE_PROVIDER
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix"); = Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
private static final String DONT_KILL_MY_APP_LINK private static final String DONT_KILL_MY_APP_URL
= "https://dontkillmyapp.com"; = "https://dontkillmyapp.com/";
private static final Route DONT_KILL_MY_APP_MANUFACTURER_API
= new Route(GET, "/api/v2/{manufacturer}.json");
private static final String DONT_KILL_MY_APP_NAME_PARAMETER
= "?app=MicroG";
private static final String BUILD_MANUFACTURER
= Build.MANUFACTURER.toLowerCase(Locale.ROOT).replace(" ", "-");
/**
* If a manufacturer specific page exists on DontKillMyApp.
*/
@Nullable
private static volatile Boolean DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
private static void open(String queryOrLink) { private static void open(String queryOrLink) {
Logger.printInfo(() -> "Opening link: " + queryOrLink);
Intent intent; Intent intent;
try { try {
// Check if queryOrLink is a valid URL. // Check if queryOrLink is a valid URL.
@@ -88,7 +107,7 @@ public class GmsCoreSupport {
// Do not exit. If the app exits before launch completes (and without // Do not exit. If the app exits before launch completes (and without
// opening another activity), then on some devices such as Pixel phone Android 10 // opening another activity), then on some devices such as Pixel phone Android 10
// no toast will be shown and the app will continually be relaunched // no toast will be shown and the app will continually relaunch
// with the appearance of a hung app. // with the appearance of a hung app.
} }
@@ -124,11 +143,12 @@ public class GmsCoreSupport {
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) { try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
if (client == null) { if (client == null) {
Logger.printInfo(() -> "GmsCore is not running in the background"); Logger.printInfo(() -> "GmsCore is not running in the background");
checkIfDontKillMyAppSupportsManufacturer();
showBatteryOptimizationDialog(context, showBatteryOptimizationDialog(context,
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message", "gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
"gms_core_dialog_open_website_text", "gms_core_dialog_open_website_text",
(dialog, id) -> open(DONT_KILL_MY_APP_LINK)); (dialog, id) -> openDontKillMyApp());
} }
} }
} catch (Exception ex) { } catch (Exception ex) {
@@ -143,6 +163,48 @@ public class GmsCoreSupport {
activity.startActivityForResult(intent, 0); activity.startActivityForResult(intent, 0);
} }
private static void checkIfDontKillMyAppSupportsManufacturer() {
Utils.runOnBackgroundThread(() -> {
try {
final long start = System.currentTimeMillis();
HttpURLConnection connection = Requester.getConnectionFromRoute(
DONT_KILL_MY_APP_URL, DONT_KILL_MY_APP_MANUFACTURER_API, BUILD_MANUFACTURER);
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
final boolean supported = connection.getResponseCode() == 200;
Logger.printInfo(() -> "Manufacturer is " + (supported ? "" : "NOT ")
+ "listed on DontKillMyApp: " + BUILD_MANUFACTURER
+ " fetch took: " + (System.currentTimeMillis() - start) + "ms");
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = supported;
} catch (Exception ex) {
Logger.printInfo(() -> "Could not check if manufacturer is listed on DontKillMyApp: "
+ BUILD_MANUFACTURER, ex);
DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED = null;
}
});
}
private static void openDontKillMyApp() {
final Boolean manufacturerSupported = DONT_KILL_MY_APP_MANUFACTURER_SUPPORTED;
String manufacturerPageToOpen;
if (manufacturerSupported == null) {
// Fetch has not completed yet. Only happens on extremely slow internet connections
// and the user spends less than 1 second reading what's on screen.
// Instead of waiting for the fetch (which may timeout),
// open the website without a vendor.
manufacturerPageToOpen = "";
} else if (manufacturerSupported) {
manufacturerPageToOpen = BUILD_MANUFACTURER;
} else {
// No manufacturer specific page exists. Open the general page.
manufacturerPageToOpen = "general";
}
open(DONT_KILL_MY_APP_URL + manufacturerPageToOpen + DONT_KILL_MY_APP_NAME_PARAMETER);
}
/** /**
* @return If GmsCore is not whitelisted from battery optimizations. * @return If GmsCore is not whitelisted from battery optimizations.
*/ */

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;
@@ -370,7 +371,7 @@ public class Utils {
if (language != AppLanguage.DEFAULT) { if (language != AppLanguage.DEFAULT) {
// Create a new context with the desired language. // Create a new context with the desired language.
Logger.printDebug(() -> "Using app language: " + language); Logger.printDebug(() -> "Using app language: " + language);
Configuration config = appContext.getResources().getConfiguration(); Configuration config = new Configuration(appContext.getResources().getConfiguration());
config.setLocale(language.getLocale()); config.setLocale(language.getLocale());
context = appContext.createConfigurationContext(config); context = appContext.createConfigurationContext(config);
} }
@@ -390,16 +391,47 @@ public class Utils {
private static Boolean isRightToLeftTextLayout; private static Boolean isRightToLeftTextLayout;
/** /**
* If the device language uses right to left text layout (hebrew, arabic, etc) * @return If the device language uses right to left text layout (Hebrew, Arabic, etc).
* If this should match any ReVanced language override then instead use
* {@link #isRightToLeftLocale(Locale)} with {@link BaseSettings#REVANCED_LANGUAGE}.
* This is the default locale of the device, which may differ if
* {@link BaseSettings#REVANCED_LANGUAGE} is set to a different language.
*/ */
public static boolean isRightToLeftTextLayout() { public static boolean isRightToLeftLocale() {
if (isRightToLeftTextLayout == null) { if (isRightToLeftTextLayout == null) {
String displayLanguage = Locale.getDefault().getDisplayLanguage(); isRightToLeftTextLayout = isRightToLeftLocale(Locale.getDefault());
isRightToLeftTextLayout = new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft();
} }
return isRightToLeftTextLayout; return isRightToLeftTextLayout;
} }
/**
* @return If the locale uses right to left text layout (Hebrew, Arabic, etc).
*/
public static boolean isRightToLeftLocale(Locale locale) {
String displayLanguage = locale.getDisplayLanguage();
return new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft();
}
/**
* @return A UTF8 string containing a left-to-right or right-to-left
* character of the device locale. If this should match any ReVanced language
* override then instead use {@link #getTextDirectionString(Locale)} with
* {@link BaseSettings#REVANCED_LANGUAGE}.
*/
public static String getTextDirectionString() {
return getTextDirectionString(isRightToLeftLocale());
}
public static String getTextDirectionString(Locale locale) {
return getTextDirectionString(isRightToLeftLocale(locale));
}
private static String getTextDirectionString(boolean isRightToLeft) {
return isRightToLeft
? "\u200F" // u200F = right to left character.
: "\u200E"; // u200E = left to right character.
}
/** /**
* @return if the text contains at least 1 number character, * @return if the text contains at least 1 number character,
* including any unicode numbers such as Arabic. * including any unicode numbers such as Arabic.
@@ -691,9 +723,10 @@ public class Utils {
/** /**
* Strips all punctuation and converts to lower case. A null parameter returns an empty string. * Strips all punctuation and converts to lower case. A null parameter returns an empty string.
*/ */
public static String removePunctuationConvertToLowercase(@Nullable CharSequence original) { public static String removePunctuationToLowercase(@Nullable CharSequence original) {
if (original == null) return ""; if (original == null) return "";
return punctuationPattern.matcher(original).replaceAll("").toLowerCase(); return punctuationPattern.matcher(original).replaceAll("")
.toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale());
} }
/** /**
@@ -725,7 +758,7 @@ public class Utils {
final String sortValue; final String sortValue;
switch (preferenceSort) { switch (preferenceSort) {
case BY_TITLE: case BY_TITLE:
sortValue = removePunctuationConvertToLowercase(preference.getTitle()); sortValue = removePunctuationToLowercase(preference.getTitle());
break; break;
case BY_KEY: case BY_KEY:
sortValue = preference.getKey(); sortValue = preference.getKey();
@@ -799,4 +832,22 @@ 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);
}
public static int clamp(int value, int lower, int upper) {
return Math.max(lower, Math.min(value, upper));
}
public static float clamp(float value, float lower, float upper) {
return Math.max(lower, Math.min(value, upper));
}
} }

View File

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

View File

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

View File

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

@@ -22,12 +22,23 @@ import app.revanced.extension.shared.settings.Setting;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public abstract class AbstractPreferenceFragment extends PreferenceFragment { public abstract class AbstractPreferenceFragment extends PreferenceFragment {
/** /**
* Indicates that if a preference changes, * Indicates that if a preference changes,
* to apply the change from the Setting to the UI component. * to apply the change from the Setting to the UI component.
*/ */
public static boolean settingImportInProgress; public static boolean settingImportInProgress;
/**
* Prevents recursive calls during preference <-> UI syncing from showing extra dialogs.
*/
private static boolean updatingPreference;
/**
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
*/
private static boolean showingUserDialogMessage;
/** /**
* Confirm and restart dialog button text and title. * Confirm and restart dialog button text and title.
* Set by subclasses if Strings cannot be added as a resource. * Set by subclasses if Strings cannot be added as a resource.
@@ -35,13 +46,13 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
@Nullable @Nullable
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle; protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
/**
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
*/
private boolean showingUserDialogMessage;
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
try { try {
if (updatingPreference) {
Logger.printDebug(() -> "Ignoring preference change as sync is in progress");
return;
}
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str)); Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
if (setting == null) { if (setting == null) {
return; return;
@@ -63,16 +74,18 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
} }
} }
updatingPreference = true;
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'. // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
// Updating here can can cause a recursive call back into this same method.
updatePreference(pref, setting, true, settingImportInProgress); updatePreference(pref, setting, true, settingImportInProgress);
// Update any other preference availability that may now be different. // Update any other preference availability that may now be different.
updateUIAvailability(); updateUIAvailability();
updatingPreference = false;
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex); Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
} }
}; };
/** /**
* Initialize this instance, and do any custom behavior. * Initialize this instance, and do any custom behavior.
* <p> * <p>
@@ -81,7 +94,10 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* so all app specific {@link Setting} instances are loaded before this method returns. * so all app specific {@link Setting} instances are loaded before this method returns.
*/ */
protected void initialize() { protected void initialize() {
final var identifier = Utils.getResourceIdentifier("revanced_prefs", "xml"); String preferenceResourceName = BaseSettings.SHOW_MENU_ICONS.get()
? "revanced_prefs_icons"
: "revanced_prefs";
final var identifier = Utils.getResourceIdentifier(preferenceResourceName, "xml");
if (identifier == 0) return; if (identifier == 0) return;
addPreferencesFromResource(identifier); addPreferencesFromResource(identifier);
@@ -97,7 +113,9 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (confirmDialogTitle == null) { if (confirmDialogTitle == null) {
confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title"); confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title");
} }
showingUserDialogMessage = true; showingUserDialogMessage = true;
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setTitle(confirmDialogTitle) .setTitle(confirmDialogTitle)
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString()) .setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
@@ -141,14 +159,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* @return If the preference is currently set to the default value of the Setting. * @return If the preference is currently set to the default value of the Setting.
*/ */
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) { protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
Object defaultValue = setting.defaultValue;
if (pref instanceof SwitchPreference switchPref) { if (pref instanceof SwitchPreference switchPref) {
return switchPref.isChecked() == (Boolean) setting.defaultValue; return switchPref.isChecked() == (Boolean) defaultValue;
} }
String defaultValueString = defaultValue.toString();
if (pref instanceof EditTextPreference editPreference) { if (pref instanceof EditTextPreference editPreference) {
return editPreference.getText().equals(setting.defaultValue.toString()); return editPreference.getText().equals(defaultValueString);
} }
if (pref instanceof ListPreference listPref) { if (pref instanceof ListPreference listPref) {
return listPref.getValue().equals(setting.defaultValue.toString()); return listPref.getValue().equals(defaultValueString);
} }
throw new IllegalStateException("Must override method to handle " throw new IllegalStateException("Must override method to handle "
@@ -255,7 +275,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
} }
} }
public static void showRestartDialog(@NonNull final Context context) { public static void showRestartDialog(Context context) {
Utils.verifyOnMainThread(); Utils.verifyOnMainThread();
if (restartDialogTitle == null) { if (restartDialogTitle == null) {
restartDialogTitle = str("revanced_settings_restart_title"); restartDialogTitle = str("revanced_settings_restart_title");
@@ -263,6 +283,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (restartDialogButtonText == null) { if (restartDialogButtonText == null) {
restartDialogButtonText = str("revanced_settings_restart"); restartDialogButtonText = str("revanced_settings_restart");
} }
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setMessage(restartDialogTitle) .setMessage(restartDialogTitle)
.setPositiveButton(restartDialogButtonText, (dialog, id) .setPositiveButton(restartDialogButtonText, (dialog, id)

View File

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

View File

@@ -1,5 +1,7 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
@@ -8,17 +10,23 @@ import android.util.AttributeSet;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import app.revanced.extension.shared.Utils; import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.Logger;
import java.util.Objects; import java.util.Objects;
import static app.revanced.extension.shared.StringRef.str; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class ResettableEditTextPreference extends EditTextPreference { public class ResettableEditTextPreference extends EditTextPreference {
/**
* Setting to reset.
*/
@Nullable
private Setting<?> setting;
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
} }
@@ -32,12 +40,22 @@ public class ResettableEditTextPreference extends EditTextPreference {
super(context); super(context);
} }
public void setSetting(@Nullable Setting<?> setting) {
this.setting = setting;
}
@Override @Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder); super.onPrepareDialogBuilder(builder);
Utils.setEditTextDialogTheme(builder); Utils.setEditTextDialogTheme(builder);
Setting<?> setting = Setting.getSettingFromPath(getKey()); if (setting == null) {
String key = getKey();
if (key != null) {
setting = Setting.getSettingFromPath(key);
}
}
if (setting != null) { if (setting != null) {
builder.setNeutralButton(str("revanced_settings_reset"), null); builder.setNeutralButton(str("revanced_settings_reset"), null);
} }
@@ -54,8 +72,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
} }
button.setOnClickListener(v -> { button.setOnClickListener(v -> {
try { try {
Setting<?> setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey())); String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
String defaultStringValue = setting.defaultValue.toString();
EditText editText = getEditText(); EditText editText = getEditText();
editText.setText(defaultStringValue); editText.setText(defaultStringValue);
editText.setSelection(defaultStringValue.length()); // move cursor to end of text editText.setSelection(defaultStringValue.length()); // move cursor to end of text

View File

@@ -0,0 +1,107 @@
package app.revanced.extension.shared.settings.preference;
import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.util.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import app.revanced.extension.shared.Utils;
/**
* PreferenceList that sorts itself.
* By default the first entry is preserved in its original position,
* and all other entries are sorted alphabetically.
*
* Ideally the 'keep first entries to preserve' is an xml parameter,
* but currently that's not so simple since Extensions code cannot use
* generated code from the Patches repo (which is required for custom xml parameters).
*
* If any class wants to use a different getFirstEntriesToPreserve value,
* it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}.
*/
@SuppressWarnings({"unused", "deprecation"})
public class SortedListPreference extends ListPreference {
/**
* Sorts the current list entries.
*
* @param firstEntriesToPreserve The number of entries to preserve in their original position.
*/
public void sortEntryAndValues(int firstEntriesToPreserve) {
CharSequence[] entries = getEntries();
CharSequence[] entryValues = getEntryValues();
if (entries == null || entryValues == null) {
return;
}
final int entrySize = entries.length;
if (entrySize != entryValues.length) {
// Xml array declaration has a missing/extra entry.
throw new IllegalStateException();
}
List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve);
SortedMap<String, Pair<CharSequence, CharSequence>> lastEntries = new TreeMap<>();
for (int i = 0; i < entrySize; i++) {
Pair<CharSequence, CharSequence> pair = new Pair<>(entries[i], entryValues[i]);
if (i < firstEntriesToPreserve) {
firstEntries.add(pair);
} else {
lastEntries.put(Utils.removePunctuationToLowercase(pair.first), pair);
}
}
CharSequence[] sortedEntries = new CharSequence[entrySize];
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
int i = 0;
for (Pair<CharSequence, CharSequence> pair : firstEntries) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
for (Pair<CharSequence, CharSequence> pair : lastEntries.values()) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
super.setEntries(sortedEntries);
super.setEntryValues(sortedEntryValues);
}
protected int getFirstEntriesToPreserve() {
return 1;
}
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
sortEntryAndValues(getFirstEntriesToPreserve());
}
public SortedListPreference(Context context) {
super(context);
sortEntryAndValues(getFirstEntriesToPreserve());
}
}

View File

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

View File

@@ -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,16 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:spotify:stub"))
compileOnly(libs.annotation)
}
android {
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

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

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

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

View File

@@ -0,0 +1,17 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,4 +33,8 @@ public class Aweme {
public AwemeStatistics getStatistics() { public AwemeStatistics getStatistics() {
throw new UnsupportedOperationException("Stub"); throw new UnsupportedOperationException("Stub");
} }
public String getShareUrl() {
throw new UnsupportedOperationException("Stub");
}
} }

View File

@@ -0,0 +1,8 @@
package com.ss.android.ugc.aweme.follow.presenter;
import com.ss.android.ugc.aweme.feed.model.Aweme;
//Dummy class
public class FollowFeed {
public Aweme aweme;
}

View File

@@ -0,0 +1,8 @@
package com.ss.android.ugc.aweme.follow.presenter;
import java.util.List;
//Dummy class
public class FollowFeedList {
public List<FollowFeed> mItems;
}

View File

@@ -163,7 +163,7 @@ internal object TwiFucker {
private fun JSONObject.entryIsWhoToFollow(): Boolean = private fun JSONObject.entryIsWhoToFollow(): Boolean =
optString("entryId").let { optString("entryId").let {
it.startsWith("whoToFollow-") || it.startsWith("who-to-follow-") || it.startsWith("connect-module-") it.startsWith("whoToFollow-") || it.startsWith("who-to-follow-") || it.startsWith("connect-module-") || it.startsWith("who-to-subscribe-")
} }
private fun JSONObject.itemContainsPromotedUser(): Boolean = private fun JSONObject.itemContainsPromotedUser(): Boolean =

View File

@@ -1,8 +1,18 @@
package app.revanced.extension.youtube; package app.revanced.extension.youtube;
import android.app.Activity; import static app.revanced.extension.shared.Utils.clamp;
import android.graphics.Color;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.text.style.ReplacementSpan;
import android.text.TextPaint;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
@@ -45,13 +55,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 +92,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 +110,62 @@ public class ThemeHelper {
? "yt_black3" ? "yt_black3"
: "yt_white1"; : "yt_white1";
return getColorInt(colorName); return Utils.getColorFromString(colorName);
}
/**
* Sets the system navigation bar color for the activity.
* Applies the background color obtained from {@link #getBackgroundColor()} to the navigation bar.
* For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility.
*/
public static void setNavigationBarColor(@Nullable Window window) {
if (window == null) {
Logger.printDebug(() -> "Cannot set navigation bar color, window is null");
return;
}
window.setNavigationBarColor(getBackgroundColor());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
}
/**
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
* <p>
* If the factor is greater than 1, the color is lightened by interpolating toward white (#FFFFFF).
* If the factor is less than or equal to 1, the color is darkened by scaling its RGB components toward black (#000000).
* The alpha channel remains unchanged.
*
* @param color The input color to adjust, in ARGB format.
* @param factor The adjustment factor. Use values > 1.0f to lighten (e.g., 1.11f for slight lightening)
* or values <= 1.0f to darken (e.g., 0.95f for slight darkening).
* @return The adjusted color in ARGB format.
*/
public static int adjustColorBrightness(int color, float factor) {
final int alpha = Color.alpha(color);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
if (factor > 1.0f) {
// Lighten: Interpolate toward white (255)
final float t = 1.0f - (1.0f / factor); // Interpolation parameter
red = Math.round(red + (255 - red) * t);
green = Math.round(green + (255 - green) * t);
blue = Math.round(blue + (255 - blue) * t);
} else {
// Darken or no change: Scale toward black
red = (int) (red * factor);
green = (int) (green * factor);
blue = (int) (blue * factor);
}
// Ensure values are within [0, 255]
red = clamp(red, 0, 255);
green = clamp(green, 0, 255);
blue = clamp(blue, 0, 255);
return Color.argb(alpha, red, green, blue);
} }
} }

View File

@@ -0,0 +1,28 @@
package app.revanced.extension.youtube.patches;
import static app.revanced.extension.shared.StringRef.sf;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
@SuppressWarnings("unused")
public class AccountCredentialsInvalidTextPatch {
/**
* Injection point.
*/
public static String getOfflineNetworkErrorString(String original) {
try {
if (Utils.isNetworkConnected()) {
Logger.printDebug(() -> "Network appears to be online, but app is showing offline error");
return '\n' + sf("microg_offline_account_login_error").toString();
}
Logger.printDebug(() -> "Network is offline");
} catch (Exception ex) {
Logger.printException(() -> "getOfflineNetworkErrorString failure", ex);
}
return original;
}
}

View File

@@ -1,6 +1,7 @@
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.PlayerType;
import app.revanced.extension.youtube.shared.ShortsPlayerState; import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -23,7 +24,13 @@ public class BackgroundPlaybackPatch {
// 7. Close the Short // 7. Close the Short
// 8. Resume playing the regular video // 8. Resume playing the regular video
// 9. Minimize the app (PIP should appear) // 9. Minimize the app (PIP should appear)
return !ShortsPlayerState.isOpen(); if (ShortsPlayerState.isOpen()) {
return false;
}
// Check if the video player is opened and it's not playing in the feed.
PlayerType current = PlayerType.getCurrent();
return !current.isNoneOrHidden() && current != PlayerType.INLINE_MINIMAL;
} }
/** /**

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.
*/
public static boolean captionsButtonDisabled;
public static boolean autoCaptionsEnabled() { /**
return Settings.AUTO_CAPTIONS.get() * Injection point.
// Do not use auto captions for Shorts. */
&& ShortsPlayerState.isOpen(); public static boolean disableAutoCaptions() {
return Settings.DISABLE_AUTO_CAPTIONS.get() && !captionsButtonStatus;
} }
/**
* Injection point.
*/
public static void setCaptionsButtonStatus(boolean status) {
captionsButtonStatus = status;
}
} }

View File

@@ -2,7 +2,7 @@ package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** @noinspection unused*/ @SuppressWarnings("unused")
public class DisableResumingStartupShortsPlayerPatch { public class DisableResumingStartupShortsPlayerPatch {
/** /**
@@ -11,4 +11,11 @@ public class DisableResumingStartupShortsPlayerPatch {
public static boolean disableResumingStartupShortsPlayer() { public static boolean disableResumingStartupShortsPlayer() {
return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get(); return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
} }
/**
* Injection point.
*/
public static boolean disableResumingStartupShortsPlayer(boolean original) {
return original && !Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
}
} }

View File

@@ -0,0 +1,13 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class HideRelatedVideoOverlayPatch {
/**
* Injection point.
*/
public static boolean hideRelatedVideoOverlay() {
return Settings.HIDE_RELATED_VIDEO_OVERLAY.get();
}
}

View File

@@ -43,10 +43,13 @@ public final class MiniplayerPatch {
MODERN_2(null, 2), MODERN_2(null, 2),
MODERN_3(null, 3), MODERN_3(null, 3),
/** /**
* Half broken miniplayer, that might be work in progress or left over abandoned code. * Works and is functional with 20.03+
* Can force this type by editing the import/export settings.
*/ */
MODERN_4(null, 4); MODERN_4(null, 4),
/**
* Half broken miniplayer, and in 20.02 and earlier is declared as type 4.
*/
MODERN_5(null, 5);
/** /**
* Legacy tablet hook value. * Legacy tablet hook value.
@@ -126,12 +129,13 @@ public final class MiniplayerPatch {
private static final boolean DRAG_AND_DROP_ENABLED = private static final boolean DRAG_AND_DROP_ENABLED =
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get(); CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
private static final boolean HIDE_EXPAND_CLOSE_ENABLED = private static final boolean HIDE_OVERLAY_BUTTONS_ENABLED =
Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.get() Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.get()
&& Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.isAvailable(); && Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.isAvailable();
private static final boolean HIDE_SUBTEXT_ENABLED = private static final boolean HIDE_SUBTEXT_ENABLED =
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_HIDE_SUBTEXT.get(); (CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3 || CURRENT_TYPE == MODERN_4)
&& Settings.MINIPLAYER_HIDE_SUBTEXT.get();
// 19.25 is last version that has forward/back buttons for phones, // 19.25 is last version that has forward/back buttons for phones,
// but buttons still show for tablets/foldable devices and they don't work well so always hide. // but buttons still show for tablets/foldable devices and they don't work well so always hide.
@@ -139,7 +143,7 @@ public final class MiniplayerPatch {
&& (VersionCheckPatch.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get()); && (VersionCheckPatch.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED = private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED =
Settings.MINIPLAYER_ROUNDED_CORNERS.get(); CURRENT_TYPE.isModern() && Settings.MINIPLAYER_ROUNDED_CORNERS.get();
private static final boolean MINIPLAYER_HORIZONTAL_DRAG_ENABLED = private static final boolean MINIPLAYER_HORIZONTAL_DRAG_ENABLED =
DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get(); DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get();
@@ -158,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;
@@ -172,11 +175,12 @@ public final class MiniplayerPatch {
} }
} }
public static final class MiniplayerHideExpandCloseAvailability implements Setting.Availability { public static final class MiniplayerHideOverlayButtonsAvailability implements Setting.Availability {
@Override @Override
public boolean isAvailable() { public boolean isAvailable() {
MiniplayerType type = Settings.MINIPLAYER_TYPE.get(); MiniplayerType type = Settings.MINIPLAYER_TYPE.get();
return (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3)) return type == MODERN_4
|| (!IS_19_20_OR_GREATER && (type == MODERN_1 || type == MODERN_3))
|| (!IS_19_26_OR_GREATER && type == MODERN_1 || (!IS_19_26_OR_GREATER && type == MODERN_1
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get()) && !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get())
|| (IS_19_29_OR_GREATER && type == MODERN_3); || (IS_19_29_OR_GREATER && type == MODERN_3);
@@ -227,9 +231,13 @@ public final class MiniplayerPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static void adjustMiniplayerOpacity(ImageView view) { public static void adjustMiniplayerOpacity(View view) {
if (CURRENT_TYPE == MODERN_1) { if (CURRENT_TYPE == MODERN_1) {
view.setImageAlpha(OPACITY_LEVEL); if (view instanceof ImageView imageView) {
imageView.setImageAlpha(OPACITY_LEVEL);
} else {
Logger.printException(() -> "Unknown miniplayer overlay view: " + view);
}
} }
} }
@@ -247,7 +255,7 @@ public final class MiniplayerPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static boolean enableMiniplayerDoubleTapAction(boolean original) { public static boolean getMiniplayerDoubleTapAction(boolean original) {
if (CURRENT_TYPE == DEFAULT) { if (CURRENT_TYPE == DEFAULT) {
return original; return original;
} }
@@ -258,7 +266,7 @@ public final class MiniplayerPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static boolean enableMiniplayerDragAndDrop(boolean original) { public static boolean getMiniplayerDragAndDrop(boolean original) {
if (CURRENT_TYPE == DEFAULT) { if (CURRENT_TYPE == DEFAULT) {
return original; return original;
} }
@@ -266,13 +274,36 @@ public final class MiniplayerPatch {
return DRAG_AND_DROP_ENABLED; return DRAG_AND_DROP_ENABLED;
} }
/**
* Injection point.
*/
public static boolean getRoundedCorners(boolean original) {
if (CURRENT_TYPE == DEFAULT) {
return original;
}
return MINIPLAYER_ROUNDED_CORNERS_ENABLED;
}
/** /**
* Injection point. * Injection point.
*/ */
public static boolean setRoundedCorners(boolean original) { public static boolean getHorizontalDrag(boolean original) {
if (CURRENT_TYPE.isModern()) { if (CURRENT_TYPE == DEFAULT) {
return MINIPLAYER_ROUNDED_CORNERS_ENABLED; return original;
}
return MINIPLAYER_HORIZONTAL_DRAG_ENABLED;
}
/**
* Injection point.
*/
public static boolean getMaximizeAnimation(boolean original) {
// This must be forced on if horizontal drag is enabled,
// otherwise the UI has visual glitches when maximizing the miniplayer.
if (MINIPLAYER_HORIZONTAL_DRAG_ENABLED) {
return true;
} }
return original; return original;
@@ -281,7 +312,7 @@ public final class MiniplayerPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static int setMiniplayerDefaultSize(int original) { public static int getMiniplayerDefaultSize(int original) {
if (CURRENT_TYPE.isModern()) { if (CURRENT_TYPE.isModern()) {
return MINIPLAYER_SIZE; return MINIPLAYER_SIZE;
} }
@@ -289,29 +320,26 @@ public final class MiniplayerPatch {
return original; return original;
} }
/**
* Injection point.
*/
public static void hideMiniplayerExpandClose(View view) {
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_OVERLAY_BUTTONS_ENABLED, view);
}
/** /**
* Injection point. * Injection point.
*/ */
public static boolean setHorizontalDrag(boolean original) { public static void hideMiniplayerActionButton(View view) {
if (CURRENT_TYPE.isModern()) { if (CURRENT_TYPE == MODERN_4) {
return MINIPLAYER_HORIZONTAL_DRAG_ENABLED; Utils.hideViewByRemovingFromParentUnderCondition(HIDE_OVERLAY_BUTTONS_ENABLED, view);
} }
return original;
} }
/** /**
* Injection point. * Injection point.
*/ */
public static void hideMiniplayerExpandClose(ImageView view) { public static void hideMiniplayerRewindForward(View view) {
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_EXPAND_CLOSE_ENABLED, view);
}
/**
* Injection point.
*/
public static void hideMiniplayerRewindForward(ImageView view) {
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_REWIND_FORWARD_ENABLED, view); Utils.hideViewByRemovingFromParentUnderCondition(HIDE_REWIND_FORWARD_ENABLED, view);
} }

View File

@@ -2,28 +2,22 @@ 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;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch; import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike; import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
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;
@@ -47,9 +41,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class ReturnYouTubeDislikePatch { public class ReturnYouTubeDislikePatch {
public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
SpoofAppVersionPatch.isSpoofingToLessThan("18.34.00");
/** /**
* RYD data for the current video on screen. * RYD data for the current video on screen.
*/ */
@@ -64,12 +55,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.
@@ -77,22 +68,31 @@ public class ReturnYouTubeDislikePatch {
@Nullable @Nullable
private static volatile String lastPrefetchedVideoId; private static volatile String lastPrefetchedVideoId;
public static void onRYDStatusChange(boolean rydEnabled) {
ReturnYouTubeDislikeApi.resetRateLimits();
// Must remove all values to protect against using stale data
// if the user enables RYD while a video is on screen.
clearData();
}
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.
@@ -156,10 +156,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);
@@ -174,7 +177,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.
@@ -182,19 +192,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);
@@ -269,7 +266,7 @@ public class ReturnYouTubeDislikePatch {
Logger.printDebug(() -> "Adding rolling number TextView changes"); Logger.printDebug(() -> "Adding rolling number TextView changes");
view.setCompoundDrawablePadding(ReturnYouTubeDislike.leftSeparatorShapePaddingPixels); view.setCompoundDrawablePadding(ReturnYouTubeDislike.leftSeparatorShapePaddingPixels);
ShapeDrawable separator = ReturnYouTubeDislike.getLeftSeparatorDrawable(); ShapeDrawable separator = ReturnYouTubeDislike.getLeftSeparatorDrawable();
if (Utils.isRightToLeftTextLayout()) { if (Utils.isRightToLeftLocale()) {
view.setCompoundDrawables(null, null, separator, null); view.setCompoundDrawables(null, null, separator, null);
} else { } else {
view.setCompoundDrawables(separator, null, null, null); view.setCompoundDrawables(separator, null, null, null);
@@ -347,137 +344,6 @@ public class ReturnYouTubeDislikePatch {
} }
} }
//
// Non litho Shorts player.
//
/**
* Replacement text to use for "Dislikes" while RYD is fetching.
*/
private static final Spannable SHORTS_LOADING_SPAN = new SpannableString("-");
/**
* Dislikes TextViews used by Shorts.
*
* Multiple TextViews are loaded at once (for the prior and next videos to swipe to).
* Keep track of all of them, and later pick out the correct one based on their on screen position.
*/
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();
private static void clearRemovedShortsTextViews() {
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
}
/**
* Injection point. Called when a Shorts dislike is updated. Always on main thread.
* Handles update asynchronously, otherwise Shorts video will be frozen while the UI thread is blocked.
*
* @return if RYD is enabled and the TextView was updated.
*/
public static boolean setShortsDislikes(@NonNull View likeDislikeView) {
try {
if (!Settings.RYD_ENABLED.get()) {
return false;
}
if (!Settings.RYD_SHORTS.get() || Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) {
// Must clear the data here, in case a new video was loaded while PlayerType
// suggested the video was not a short (can happen when spoofing to an old app version).
clearData();
return false;
}
Logger.printDebug(() -> "setShortsDislikes");
TextView textView = (TextView) likeDislikeView;
textView.setText(SHORTS_LOADING_SPAN); // Change 'Dislike' text to the loading text.
shortsTextViewRefs.add(new WeakReference<>(textView));
if (likeDislikeView.isSelected() && isShortTextViewOnScreen(textView)) {
Logger.printDebug(() -> "Shorts dislike is already selected");
ReturnYouTubeDislike videoData = currentVideoData;
if (videoData != null) videoData.setUserVote(Vote.DISLIKE);
}
// For the first short played, the Shorts dislike hook is called after the video id hook.
// But for most other times this hook is called before the video id (which is not ideal).
// Must update the TextViews here, and also after the videoId changes.
updateOnScreenShortsTextViews(false);
return true;
} catch (Exception ex) {
Logger.printException(() -> "setShortsDislikes failure", ex);
return false;
}
}
/**
* @param forceUpdate if false, then only update the 'loading text views.
* If true, update all on screen text views.
*/
private static void updateOnScreenShortsTextViews(boolean forceUpdate) {
try {
clearRemovedShortsTextViews();
if (shortsTextViewRefs.isEmpty()) {
return;
}
ReturnYouTubeDislike videoData = currentVideoData;
if (videoData == null) {
return;
}
Logger.printDebug(() -> "updateShortsTextViews");
Runnable update = () -> {
Spanned shortsDislikesSpan = videoData.getDislikeSpanForShort(SHORTS_LOADING_SPAN);
Utils.runOnMainThreadNowOrLater(() -> {
String videoId = videoData.getVideoId();
if (!videoId.equals(VideoInformation.getVideoId())) {
// User swiped to new video before fetch completed
Logger.printDebug(() -> "Ignoring stale dislikes data for short: " + videoId);
return;
}
// Update text views that appear to be visible on screen.
// Only 1 will be the actual textview for the current Short,
// but discarded and not yet garbage collected views can remain.
// So must set the dislike span on all views that match.
for (WeakReference<TextView> textViewRef : shortsTextViewRefs) {
TextView textView = textViewRef.get();
if (textView == null) {
continue;
}
if (isShortTextViewOnScreen(textView)
&& (forceUpdate || textView.getText().toString().equals(SHORTS_LOADING_SPAN.toString()))) {
Logger.printDebug(() -> "Setting Shorts TextView to: " + shortsDislikesSpan);
textView.setText(shortsDislikesSpan);
}
}
});
};
if (videoData.fetchCompleted()) {
update.run(); // Network call is completed, no need to wait on background thread.
} else {
Utils.runOnBackgroundThread(update);
}
} catch (Exception ex) {
Logger.printException(() -> "updateOnScreenShortsTextViews failure", ex);
}
}
/**
* Check if a view is within the screen bounds.
*/
private static boolean isShortTextViewOnScreen(@NonNull View view) {
final int[] location = new int[2];
view.getLocationInWindow(location);
if (location[0] <= 0 && location[1] <= 0) { // Lower bound
return false;
}
Rect windowRect = new Rect();
view.getWindowVisibleDisplayFrame(windowRect); // Upper bound
return location[0] < windowRect.width() && location[1] < windowRect.height();
}
// //
// Video Id and voting hooks (all players). // Video Id and voting hooks (all players).
// //
@@ -503,8 +369,7 @@ public class ReturnYouTubeDislikePatch {
if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get())) { if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get())) {
return; return;
} }
final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER final boolean waitForFetchToComplete = videoIdIsShort && !lastPlayerResponseWasShort;
&& videoIdIsShort && !lastPlayerResponseWasShort;
Logger.printDebug(() -> "Prefetching RYD for video: " + videoId); Logger.printDebug(() -> "Prefetching RYD for video: " + videoId);
ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId); ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId);
@@ -557,12 +422,6 @@ public class ReturnYouTubeDislikePatch {
data.setVideoIdIsShort(true); data.setVideoIdIsShort(true);
} }
currentVideoData = data; currentVideoData = data;
// Current video id hook can be called out of order with the non litho Shorts text view hook.
// Must manually update again here.
if (isNoneHiddenOrSlidingMinimized) {
updateOnScreenShortsTextViews(true);
}
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "newVideoLoaded failure", ex); Logger.printException(() -> "newVideoLoaded failure", ex);
} }
@@ -587,7 +446,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) {
@@ -622,13 +484,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

@@ -2,6 +2,8 @@ package app.revanced.extension.youtube.patches;
import android.app.Activity; import android.app.Activity;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Objects; import java.util.Objects;
@@ -76,7 +78,7 @@ public class ShortsAutoplayPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) { public static Enum<?> changeShortsRepeatBehavior(@Nullable Enum<?> original) {
try { try {
final boolean autoplay; final boolean autoplay;
@@ -98,17 +100,35 @@ public class ShortsAutoplayPatch {
: ShortsLoopBehavior.REPEAT; : ShortsLoopBehavior.REPEAT;
if (behavior.ytEnumValue != null) { if (behavior.ytEnumValue != null) {
Logger.printDebug(() -> behavior.ytEnumValue == original Logger.printDebug(() -> {
? "Changing Shorts repeat behavior from: " + original.name() + " to: " + behavior.ytEnumValue String name = (original == null ? "unknown (null)" : original.name());
: "Behavior setting is same as original. Using original: " + original.name() return behavior == original
); ? "Behavior setting is same as original. Using original: " + name
: "Changing Shorts repeat behavior from: " + name + " to: " + behavior.name();
});
return behavior.ytEnumValue; return behavior.ytEnumValue;
} }
if (original == null) {
// Cannot return null, as null is used to indicate Short was auto played.
// Unpatched app replaces null with unknown enum type (appears to fix for bad api data).
Enum<?> unknown = ShortsLoopBehavior.UNKNOWN.ytEnumValue;
Logger.printDebug(() -> "Original is null, returning: " + unknown.name());
return unknown;
}
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "changeShortsRepeatState failure", ex); Logger.printException(() -> "changeShortsRepeatBehavior failure", ex);
} }
return original; return original;
} }
/**
* Injection point.
*/
public static boolean isAutoPlay(Enum<?> original) {
return ShortsLoopBehavior.SINGLE_PLAY.ytEnumValue == original;
}
} }

View File

@@ -3,11 +3,15 @@ package app.revanced.extension.youtube.patches;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
public class VersionCheckPatch { public class VersionCheckPatch {
public static final boolean IS_19_17_OR_GREATER = Utils.getAppVersionName().compareTo("19.17.00") >= 0; private static boolean isVersionOrGreater(String version) {
public static final boolean IS_19_20_OR_GREATER = Utils.getAppVersionName().compareTo("19.20.00") >= 0; return Utils.getAppVersionName().compareTo(version) >= 0;
public static final boolean IS_19_21_OR_GREATER = Utils.getAppVersionName().compareTo("19.21.00") >= 0; }
public static final boolean IS_19_26_OR_GREATER = Utils.getAppVersionName().compareTo("19.26.00") >= 0;
public static final boolean IS_19_29_OR_GREATER = Utils.getAppVersionName().compareTo("19.29.00") >= 0; public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
public static final boolean IS_19_34_OR_GREATER = Utils.getAppVersionName().compareTo("19.34.00") >= 0; public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
public static final boolean IS_19_46_OR_GREATER = Utils.getAppVersionName().compareTo("19.46.00") >= 0; public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
} }

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.isRightToLeftLocale()) {
searchBarView.setPadding(paddingLeft, paddingTop, paddingStart, paddingBottom);
} else {
searchBarView.setPadding(paddingStart, paddingTop, paddingRight, paddingBottom);
}
} catch (Exception ex) {
Logger.printException(() -> "setActionBar failure", ex);
}
} }
} }

View File

@@ -74,6 +74,7 @@ public final class AdsFilter extends Filter {
"video_display_button_group_layout", "video_display_button_group_layout",
"landscape_image_wide_button_layout", "landscape_image_wide_button_layout",
"video_display_carousel_button_group_layout", "video_display_carousel_button_group_layout",
"video_display_full_buttoned_short_dr_layout",
"compact_landscape_image_layout", // Tablet layout search results. "compact_landscape_image_layout", // Tablet layout search results.
"text_image_no_button_layout" // Tablet layout search results. "text_image_no_button_layout" // Tablet layout search results.
); );

View File

@@ -2,20 +2,20 @@ package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.youtube.patches.playback.quality.RestoreOldVideoQualityMenuPatch; import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** /**
* Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}. * Abuse LithoFilter for {@link AdvancedVideoQualityMenuPatch}.
*/ */
public final class VideoQualityMenuFilterPatch extends Filter { public final class AdvancedVideoQualityMenuFilter extends Filter {
// Must be volatile or synchronized, as litho filtering runs off main thread // Must be volatile or synchronized, as litho filtering runs off main thread
// and this field is then access from the main thread. // and this field is then access from the main thread.
public static volatile boolean isVideoQualityMenuVisible; public static volatile boolean isVideoQualityMenuVisible;
public VideoQualityMenuFilterPatch() { public AdvancedVideoQualityMenuFilter() {
addPathCallbacks(new StringFilterGroup( addPathCallbacks(new StringFilterGroup(
Settings.RESTORE_OLD_VIDEO_QUALITY_MENU, Settings.ADVANCED_VIDEO_QUALITY_MENU,
"quick_quality_sheet_content.eml-js" "quick_quality_sheet_content.eml-js"
)); ));
} }

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

@@ -12,10 +12,12 @@ final class CommentsFilter extends Filter {
private final StringFilterGroup commentComposer; private final StringFilterGroup commentComposer;
private final ByteArrayFilterGroup emojiPickerBufferGroup; private final ByteArrayFilterGroup emojiPickerBufferGroup;
private final StringFilterGroup filterChipBar;
private final ByteArrayFilterGroup aiCommentsSummary;
public CommentsFilter() { public CommentsFilter() {
var chatSummary = new StringFilterGroup( var chatSummary = new StringFilterGroup(
Settings.HIDE_COMMENTS_CHAT_SUMMARY, Settings.HIDE_COMMENTS_AI_CHAT_SUMMARY,
"live_chat_summary_banner.eml" "live_chat_summary_banner.eml"
); );
@@ -58,6 +60,16 @@ final class CommentsFilter extends Filter {
"id.comment.quick_emoji.button" "id.comment.quick_emoji.button"
); );
filterChipBar = new StringFilterGroup(
Settings.HIDE_COMMENTS_AI_SUMMARY,
"filter_chip_bar.eml"
);
aiCommentsSummary = new ByteArrayFilterGroup(
null,
"yt_fill_spark_"
);
addPathCallbacks( addPathCallbacks(
chatSummary, chatSummary,
commentsByMembers, commentsByMembers,
@@ -65,7 +77,8 @@ final class CommentsFilter extends Filter {
createAShort, createAShort,
previewComment, previewComment,
thanksButton, thanksButton,
commentComposer commentComposer,
filterChipBar
); );
} }
@@ -84,6 +97,13 @@ final class CommentsFilter extends Filter {
return false; return false;
} }
if (matchedGroup == filterChipBar) {
if (aiCommentsSummary.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
} }
} }

View File

@@ -23,6 +23,11 @@ final class DescriptionComponentsFilter extends Filter {
"metadata" "metadata"
); );
final StringFilterGroup aiGeneratedVideoSummarySection = new StringFilterGroup(
Settings.HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION,
"cell_expandable_metadata.eml"
);
final StringFilterGroup attributesSection = new StringFilterGroup( final StringFilterGroup attributesSection = new StringFilterGroup(
Settings.HIDE_ATTRIBUTES_SECTION, Settings.HIDE_ATTRIBUTES_SECTION,
"gaming_section", "gaming_section",
@@ -67,6 +72,7 @@ final class DescriptionComponentsFilter extends Filter {
); );
addPathCallbacks( addPathCallbacks(
aiGeneratedVideoSummarySection,
attributesSection, attributesSection,
infoCardsSection, infoCardsSection,
howThisWasMadeSection, howThisWasMadeSection,

View File

@@ -34,8 +34,6 @@ public final class LayoutComponentsFilter extends Filter {
private final StringFilterGroup notifyMe; private final StringFilterGroup notifyMe;
private final StringFilterGroup singleItemInformationPanel; private final StringFilterGroup singleItemInformationPanel;
private final StringFilterGroup expandableMetadata; private final StringFilterGroup expandableMetadata;
private final ByteArrayFilterGroup searchResultRecommendations;
private final StringFilterGroup searchResultVideo;
private final StringFilterGroup compactChannelBarInner; private final StringFilterGroup compactChannelBarInner;
private final StringFilterGroup compactChannelBarInnerButton; private final StringFilterGroup compactChannelBarInnerButton;
private final ByteArrayFilterGroup joinMembershipButton; private final ByteArrayFilterGroup joinMembershipButton;
@@ -75,7 +73,10 @@ public final class LayoutComponentsFilter extends Filter {
"post_base_wrapper_slim.eml", "post_base_wrapper_slim.eml",
"poll_post_root.eml", "poll_post_root.eml",
"videos_post_root.eml", "videos_post_root.eml",
"post_shelf_slim.eml" "post_shelf_slim.eml",
"videos_post_responsive_root.eml",
"text_post_responsive_root.eml",
"poll_post_responsive_root.eml"
); );
final var communityGuidelines = new StringFilterGroup( final var communityGuidelines = new StringFilterGroup(
@@ -211,7 +212,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(
@@ -230,14 +231,9 @@ public final class LayoutComponentsFilter extends Filter {
"mixed_content_shelf" "mixed_content_shelf"
); );
searchResultVideo = new StringFilterGroup( final var searchResultRecommendationLabels = new StringFilterGroup(
Settings.HIDE_SEARCH_RESULT_RECOMMENDATIONS, Settings.HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS,
"search_video_with_context.eml" "endorsement_header_footer.eml"
);
searchResultRecommendations = new ByteArrayFilterGroup(
Settings.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
"endorsement_header_footer"
); );
horizontalShelves = new StringFilterGroup( horizontalShelves = new StringFilterGroup(
@@ -255,7 +251,7 @@ public final class LayoutComponentsFilter extends Filter {
compactChannelBar, compactChannelBar,
communityPosts, communityPosts,
paidPromotion, paidPromotion,
searchResultVideo, searchResultRecommendationLabels,
latestPosts, latestPosts,
channelWatermark, channelWatermark,
communityGuidelines, communityGuidelines,
@@ -297,13 +293,6 @@ public final class LayoutComponentsFilter extends Filter {
return false; return false;
} }
if (matchedGroup == searchResultVideo) {
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}
// The groups are excluded from the filter due to the exceptions list below. // The groups are excluded from the filter due to the exceptions list below.
// Filter them separately here. // Filter them separately here.
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
@@ -454,14 +443,24 @@ 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,
// otherwise the content shelves in the explore/music/courses pages are hidde.
if (NavigationBar.isBackButtonVisible()) {
return false;
}
// Check navigation button last. // Check navigation button last.
// Only filter if the library tab is not selected. // Only filter if the library tab is not selected.
// This check is important as the shelf layout is used for the library tab playlists. // This check is important as the shelf layout is used for the library tab playlists.

View File

@@ -87,6 +87,10 @@ public final class LithoFilterPatch {
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads. * the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
*/ */
private static final ThreadLocal<ByteBuffer> bufferThreadLocal = new ThreadLocal<>(); private static final ThreadLocal<ByteBuffer> bufferThreadLocal = new ThreadLocal<>();
/**
* Results of calling {@link #filter(String, StringBuilder)}.
*/
private static final ThreadLocal<Boolean> filterResult = new ThreadLocal<>();
static { static {
for (Filter filter : filters) { for (Filter filter : filters) {
@@ -140,11 +144,22 @@ public final class LithoFilterPatch {
} }
} }
/**
* Injection point.
*/
public static boolean shouldFilter() {
Boolean shouldFilter = filterResult.get();
return shouldFilter != null && shouldFilter;
}
/** /**
* Injection point. Called off the main thread, and commonly called by multiple threads at the same time. * Injection point. Called off the main thread, and commonly called by multiple threads at the same time.
*/ */
@SuppressWarnings("unused") public static void filter(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
public static boolean filter(@Nullable String lithoIdentifier, @NonNull StringBuilder pathBuilder) { filterResult.set(handleFiltering(lithoIdentifier, pathBuilder));
}
private static boolean handleFiltering(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
try { try {
if (pathBuilder.length() == 0) { if (pathBuilder.length() == 0) {
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

@@ -1,6 +1,5 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.components;
import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton; import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import android.view.View; import android.view.View;
@@ -52,6 +51,7 @@ public final class ShortsFilter extends Filter {
private final StringFilterGroup suggestedAction; private final StringFilterGroup suggestedAction;
private final ByteArrayFilterGroupList suggestedActionsGroupList = new ByteArrayFilterGroupList(); private final ByteArrayFilterGroupList suggestedActionsGroupList = new ByteArrayFilterGroupList();
private final StringFilterGroup shortsActionBar;
private final StringFilterGroup actionButton; private final StringFilterGroup actionButton;
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList(); private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
@@ -141,6 +141,16 @@ public final class ShortsFilter extends Filter {
"like_fountain.eml" "like_fountain.eml"
); );
StringFilterGroup likeButton = new StringFilterGroup(
Settings.HIDE_SHORTS_LIKE_BUTTON,
"shorts_like_button.eml"
);
StringFilterGroup dislikeButton = new StringFilterGroup(
Settings.HIDE_SHORTS_DISLIKE_BUTTON,
"shorts_dislike_button.eml"
);
joinButton = new StringFilterGroup( joinButton = new StringFilterGroup(
Settings.HIDE_SHORTS_JOIN_BUTTON, Settings.HIDE_SHORTS_JOIN_BUTTON,
"sponsor_button" "sponsor_button"
@@ -156,9 +166,15 @@ public final class ShortsFilter extends Filter {
"reel_player_disclosure.eml" "reel_player_disclosure.eml"
); );
shortsActionBar = new StringFilterGroup(
null,
"shorts_action_bar.eml"
);
actionButton = new StringFilterGroup( actionButton = new StringFilterGroup(
null, null,
"shorts_video_action_button.eml" // Can be simply 'button.eml' or 'shorts_video_action_button.eml'
"button.eml"
); );
suggestedAction = new StringFilterGroup( suggestedAction = new StringFilterGroup(
@@ -167,27 +183,16 @@ public final class ShortsFilter extends Filter {
); );
addPathCallbacks( addPathCallbacks(
shortsCompactFeedVideoPath, suggestedAction, actionButton, joinButton, subscribeButton, shortsCompactFeedVideoPath, joinButton, subscribeButton, paidPromotionButton,
paidPromotionButton, pausedOverlayButtons, channelBar, fullVideoLinkLabel, videoTitle, shortsActionBar, suggestedAction, pausedOverlayButtons, channelBar,
reelSoundMetadata, soundButton, infoPanel, stickers, likeFountain fullVideoLinkLabel, videoTitle, reelSoundMetadata, soundButton, infoPanel,
stickers, likeFountain, likeButton, dislikeButton
); );
// //
// Action buttons // All other action buttons.
// //
videoActionButtonGroupList.addAll( videoActionButtonGroupList.addAll(
// This also appears as the path item 'shorts_like_button.eml'
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_LIKE_BUTTON,
"reel_like_button",
"reel_like_toggled_button"
),
// This also appears as the path item 'shorts_dislike_button.eml'
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_DISLIKE_BUTTON,
"reel_dislike_button",
"reel_dislike_toggled_button"
),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_COMMENTS_BUTTON, Settings.HIDE_SHORTS_COMMENTS_BUTTON,
"reel_comment_button" "reel_comment_button"
@@ -286,9 +291,11 @@ public final class ShortsFilter extends Filter {
return false; return false;
} }
// Video action buttons (like, dislike, comment, share, remix) have the same path. // Video action buttons (comment, share, remix) have the same path.
if (matchedGroup == actionButton) { // Like and dislike are separate path filters and don't require buffer searching.
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) { if (matchedGroup == shortsActionBar) {
if (actionButton.check(path).isFiltered()
&& videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex); return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
} }
return false; return false;
@@ -392,37 +399,6 @@ public final class ShortsFilter extends Filter {
return original; return original;
} }
// region Hide the buttons in older versions of YouTube. New versions use Litho.
public static void hideLikeButton(final View likeButtonView) {
// Cannot set the visibility to gone for like/dislike,
// as some other unknown YT code also sets the visibility after this hook.
//
// Setting the view to 0dp works, but that leaves a blank space where
// the button was (only relevant for dislikes button).
//
// Instead remove the view from the parent.
Utils.hideViewByRemovingFromParentUnderCondition(Settings.HIDE_SHORTS_LIKE_BUTTON, likeButtonView);
}
public static void hideDislikeButton(final View dislikeButtonView) {
Utils.hideViewByRemovingFromParentUnderCondition(Settings.HIDE_SHORTS_DISLIKE_BUTTON, dislikeButtonView);
}
public static void hideShortsCommentsButton(final View commentsButtonView) {
hideViewUnderCondition(Settings.HIDE_SHORTS_COMMENTS_BUTTON, commentsButtonView);
}
public static void hideShortsRemixButton(final View remixButtonView) {
hideViewUnderCondition(Settings.HIDE_SHORTS_REMIX_BUTTON, remixButtonView);
}
public static void hideShortsShareButton(final View shareButtonView) {
hideViewUnderCondition(Settings.HIDE_SHORTS_SHARE_BUTTON, shareButtonView);
}
// endregion
public static void setNavigationBar(PivotBar view) { public static void setNavigationBar(PivotBar view) {
pivotBarRef = new WeakReference<>(view); pivotBarRef = new WeakReference<>(view);
} }

View File

@@ -8,30 +8,30 @@ import android.widget.ListView;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.components.VideoQualityMenuFilterPatch; import app.revanced.extension.youtube.patches.components.AdvancedVideoQualityMenuFilter;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** /**
* This patch contains the logic to show the old video quality menu. * This patch contains the logic to always open the advanced video quality menu.
* Two methods are required, because the quality menu is a RecyclerView in the new YouTube version * Two methods are required, because the quality menu is a RecyclerView in the new YouTube version
* and a ListView in the old one. * and a ListView in the old one.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class RestoreOldVideoQualityMenuPatch { public final class AdvancedVideoQualityMenuPatch {
/** /**
* Injection point. * Injection point.
*/ */
public static void onFlyoutMenuCreate(RecyclerView recyclerView) { public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
if (!Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get()) return; if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
recyclerView.getViewTreeObserver().addOnDrawListener(() -> { recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
try { try {
// Check if the current view is the quality menu. // Check if the current view is the quality menu.
if (!VideoQualityMenuFilterPatch.isVideoQualityMenuVisible || recyclerView.getChildCount() == 0) { if (!AdvancedVideoQualityMenuFilter.isVideoQualityMenuVisible || recyclerView.getChildCount() == 0) {
return; return;
} }
VideoQualityMenuFilterPatch.isVideoQualityMenuVisible = false; AdvancedVideoQualityMenuFilter.isVideoQualityMenuVisible = false;
ViewParent quickQualityViewParent = Utils.getParentView(recyclerView, 3); ViewParent quickQualityViewParent = Utils.getParentView(recyclerView, 3);
if (!(quickQualityViewParent instanceof ViewGroup)) { if (!(quickQualityViewParent instanceof ViewGroup)) {
@@ -39,16 +39,15 @@ public final class RestoreOldVideoQualityMenuPatch {
} }
View firstChild = recyclerView.getChildAt(0); View firstChild = recyclerView.getChildAt(0);
if (!(firstChild instanceof ViewGroup)) { if (!(firstChild instanceof ViewGroup firstChildGroup)) {
return; return;
} }
ViewGroup advancedQualityParentView = (ViewGroup) firstChild; if (firstChildGroup.getChildCount() < 4) {
if (advancedQualityParentView.getChildCount() < 4) {
return; return;
} }
View advancedQualityView = advancedQualityParentView.getChildAt(3); View advancedQualityView = firstChildGroup.getChildAt(3);
if (advancedQualityView == null) { if (advancedQualityView == null) {
return; return;
} }
@@ -71,7 +70,7 @@ public final class RestoreOldVideoQualityMenuPatch {
* Used to force the creation of the advanced menu item for the Shorts quality flyout. * Used to force the creation of the advanced menu item for the Shorts quality flyout.
*/ */
public static boolean forceAdvancedVideoQualityMenuCreation(boolean original) { public static boolean forceAdvancedVideoQualityMenuCreation(boolean original) {
return Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get() || original; return Settings.ADVANCED_VIDEO_QUALITY_MENU.get() || original;
} }
/** /**
@@ -79,8 +78,8 @@ public final class RestoreOldVideoQualityMenuPatch {
* *
* Used if spoofing to an old app version, and also used for the Shorts video quality flyout. * Used if spoofing to an old app version, and also used for the Shorts video quality flyout.
*/ */
public static void showOldVideoQualityMenu(final ListView listView) { public static void showAdvancedVideoQualityMenu(ListView listView) {
if (!Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get()) return; if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() { listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override @Override

View File

@@ -1,16 +1,12 @@
package app.revanced.extension.youtube.patches.playback.speed; package app.revanced.extension.youtube.patches.playback.speed;
import static app.revanced.extension.shared.StringRef.sf;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.preference.ListPreference;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent; import android.view.ViewParent;
import androidx.annotation.NonNull;
import java.util.Arrays; import java.util.Arrays;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
@@ -21,8 +17,6 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class CustomPlaybackSpeedPatch { public class CustomPlaybackSpeedPatch {
private static final float PLAYBACK_SPEED_AUTO = Settings.PLAYBACK_SPEED_DEFAULT.defaultValue;
/** /**
* Maximum playback speed, exclusive value. Custom speeds must be less than this value. * Maximum playback speed, exclusive value. Custom speeds must be less than this value.
* <p> * <p>
@@ -47,19 +41,14 @@ public class CustomPlaybackSpeedPatch {
*/ */
private static long lastTimeOldPlaybackMenuInvoked; private static long lastTimeOldPlaybackMenuInvoked;
/**
* PreferenceList entries and values, of all available playback speeds.
*/
private static String[] preferenceListEntries, preferenceListEntryValues;
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();
@@ -117,33 +106,6 @@ public class CustomPlaybackSpeedPatch {
return false; return false;
} }
/**
* Initialize a settings preference list with the available playback speeds.
*/
@SuppressWarnings("deprecation")
public static void initializeListPreference(ListPreference preference) {
if (preferenceListEntries == null) {
final int numberOfEntries = customPlaybackSpeeds.length + 1;
preferenceListEntries = new String[numberOfEntries];
preferenceListEntryValues = new String[numberOfEntries];
// Auto speed (same behavior as unpatched).
preferenceListEntries[0] = sf("revanced_custom_playback_speeds_auto").toString();
preferenceListEntryValues[0] = String.valueOf(PLAYBACK_SPEED_AUTO);
int i = 1;
for (float speed : customPlaybackSpeeds) {
String speedString = String.valueOf(speed);
preferenceListEntries[i] = speedString + "x";
preferenceListEntryValues[i] = speedString;
i++;
}
}
preference.setEntries(preferenceListEntries);
preference.setEntryValues(preferenceListEntryValues);
}
/** /**
* Injection point. * Injection point.
*/ */

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.youtube.patches.theme; package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.clamp;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Color; import android.graphics.Color;
@@ -226,6 +227,7 @@ public final class SeekbarColorPatch {
} }
private static String loadRawResourceAsString(int resourceId) { private static String loadRawResourceAsString(int resourceId) {
//noinspection CharsetObjectCanBeUsed
try (InputStream inputStream = Utils.getContext().getResources().openRawResource(resourceId); try (InputStream inputStream = Utils.getContext().getResources().openRawResource(resourceId);
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) { Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
return scanner.next(); return scanner.next();
@@ -281,6 +283,20 @@ public final class SeekbarColorPatch {
/** /**
* Injection point. * Injection point.
* 19.49+
*/
public static int[] getPlayerLinearGradient(int[] original, int x0, int y1) {
// This hook is used for both the player and the feed.
// Feed usage always has x0 and y1 value of zero, and the player is always non zero.
if (HIDE_SEEKBAR_THUMBNAIL_ENABLED && x0 == 0 && y1 == 0) {
return HIDDEN_SEEKBAR_GRADIENT_COLORS;
}
return getPlayerLinearGradient(original);
}
/**
* Injection point.
* Pre 19.49
*/ */
public static int[] getPlayerLinearGradient(int[] original) { public static int[] getPlayerLinearGradient(int[] original) {
return SEEKBAR_CUSTOM_COLOR_ENABLED return SEEKBAR_CUSTOM_COLOR_ENABLED
@@ -363,14 +379,4 @@ public final class SeekbarColorPatch {
return originalColor; return originalColor;
} }
} }
/** @noinspection SameParameterValue */
private static int clamp(int value, int lower, int upper) {
return Math.max(lower, Math.min(value, upper));
}
/** @noinspection SameParameterValue */
private static float clamp(float value, float lower, float upper) {
return Math.max(lower, Math.min(value, upper));
}
} }

View File

@@ -37,7 +37,6 @@ import java.util.concurrent.*;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ThemeHelper; import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData; import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi; import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@@ -87,9 +86,6 @@ public class ReturnYouTubeDislike {
*/ */
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye' private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
private static final boolean IS_SPOOFING_TO_OLD_SEPARATOR_COLOR
= SpoofAppVersionPatch.isSpoofingToLessThan("18.10.00");
/** /**
* Cached lookup of all video ids. * Cached lookup of all video ids.
*/ */
@@ -118,7 +114,7 @@ public class ReturnYouTubeDislike {
private static final Rect middleSeparatorBounds; private static final Rect middleSeparatorBounds;
/** /**
* Left separator horizontal padding for Rolling Number layout. * Horizontal padding between the left and middle separator.
*/ */
public static final int leftSeparatorShapePaddingPixels; public static final int leftSeparatorShapePaddingPixels;
private static final ShapeDrawable leftSeparatorShape; private static final ShapeDrawable leftSeparatorShape;
@@ -133,7 +129,7 @@ public class ReturnYouTubeDislike {
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp); (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize); middleSeparatorBounds = new Rect(0, 0, middleSeparatorSize, middleSeparatorSize);
leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10.0f, dp); leftSeparatorShapePaddingPixels = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8.4f, dp);
leftSeparatorShape = new ShapeDrawable(new RectShape()); leftSeparatorShape = new ShapeDrawable(new RectShape());
leftSeparatorShape.setBounds(leftSeparatorBounds); leftSeparatorShape.setBounds(leftSeparatorBounds);
@@ -184,17 +180,8 @@ public class ReturnYouTubeDislike {
* Color of the left and middle separator, based on the color of the right separator. * Color of the left and middle separator, based on the color of the right separator.
* It's unknown where YT gets the color from, and the values here are approximated by hand. * It's unknown where YT gets the color from, and the values here are approximated by hand.
* Ideally, this would be the actual color YT uses at runtime. * Ideally, this would be the actual color YT uses at runtime.
*
* Older versions before the 'Me' library tab use a slightly different color.
* If spoofing was previously used and is now turned off,
* or an old version was recently upgraded then the old colors are sometimes still used.
*/ */
private static int getSeparatorColor() { private static int getSeparatorColor() {
if (IS_SPOOFING_TO_OLD_SEPARATOR_COLOR) {
return ThemeHelper.isDarkTheme()
? 0x29AAAAAA // transparent dark gray
: 0xFFD9D9D9; // light gray
}
return ThemeHelper.isDarkTheme() return ThemeHelper.isDarkTheme()
? 0x33FFFFFF ? 0x33FFFFFF
: 0xFFD9D9D9; : 0xFFD9D9D9;
@@ -248,10 +235,10 @@ public class ReturnYouTubeDislike {
final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get(); final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get();
if (!compactLayout) { if (!compactLayout) {
String leftSeparatorString = getTextDirectionString(); String leftSeparatorString = Utils.getTextDirectionString();
final Spannable leftSeparatorSpan; final Spannable leftSeparatorSpan;
if (isRollingNumber) { if (isRollingNumber) {
leftSeparatorSpan = new SpannableString(leftSeparatorString); leftSeparatorSpan = new SpannableString(leftSeparatorString);
} else { } else {
leftSeparatorString += " "; leftSeparatorString += " ";
leftSeparatorSpan = new SpannableString(leftSeparatorString); leftSeparatorSpan = new SpannableString(leftSeparatorString);
@@ -292,12 +279,6 @@ public class ReturnYouTubeDislike {
return new SpannableString(builder); return new SpannableString(builder);
} }
private static @NonNull String getTextDirectionString() {
return Utils.isRightToLeftTextLayout()
? "\u200F" // u200F = right to left character
: "\u200E"; // u200E = left to right character
}
/** /**
* @return If the text is likely for a previously created likes/dislikes segmented span. * @return If the text is likely for a previously created likes/dislikes segmented span.
*/ */

View File

@@ -0,0 +1,29 @@
package app.revanced.extension.youtube.returnyoutubedislike.ui;
import android.content.Context;
import android.util.AttributeSet;
import app.revanced.extension.youtube.settings.preference.UrlLinkPreference;
/**
* Allows tapping the RYD about preference to open the website.
*/
@SuppressWarnings("unused")
public class ReturnYouTubeDislikeAboutPreference extends UrlLinkPreference {
{
externalUrl = "https://returnyoutubedislike.com";
}
public ReturnYouTubeDislikeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ReturnYouTubeDislikeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ReturnYouTubeDislikeAboutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReturnYouTubeDislikeAboutPreference(Context context) {
super(context);
}
}

View File

@@ -0,0 +1,126 @@
package app.revanced.extension.youtube.returnyoutubedislike.ui;
import static app.revanced.extension.shared.StringRef.str;
import android.content.Context;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
@SuppressWarnings({"unused", "deprecation"})
public class ReturnYouTubeDislikeDebugStatsPreferenceCategory extends PreferenceCategory {
private static final boolean SHOW_RYD_DEBUG_STATS = BaseSettings.DEBUG.get();
private static String createSummaryText(int value, String summaryStringZeroKey, String summaryStringOneOrMoreKey) {
if (value == 0) {
return str(summaryStringZeroKey);
}
return str(summaryStringOneOrMoreKey, value);
}
private static String createMillisecondStringFromNumber(long number) {
return String.format(str("revanced_ryd_statistics_millisecond_text"), number);
}
public ReturnYouTubeDislikeDebugStatsPreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReturnYouTubeDislikeDebugStatsPreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ReturnYouTubeDislikeDebugStatsPreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected View onCreateView(ViewGroup parent) {
if (!SHOW_RYD_DEBUG_STATS) {
// Use an empty view to hide without removing.
return new View(getContext());
}
return super.onCreateView(parent);
}
protected void onAttachedToActivity() {
try {
super.onAttachedToActivity();
if (!SHOW_RYD_DEBUG_STATS) {
return;
}
Logger.printDebug(() -> "Updating stats preferences");
removeAll();
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallResponseTimeAverage_title",
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeAverage())
);
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallResponseTimeMin_title",
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMin())
);
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallResponseTimeMax_title",
createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMax())
);
String fetchCallTimeWaitingLastSummary;
final long fetchCallTimeWaitingLast = ReturnYouTubeDislikeApi.getFetchCallResponseTimeLast();
if (fetchCallTimeWaitingLast == ReturnYouTubeDislikeApi.FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT) {
fetchCallTimeWaitingLastSummary = str("revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary");
} else {
fetchCallTimeWaitingLastSummary = createMillisecondStringFromNumber(fetchCallTimeWaitingLast);
}
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallResponseTimeLast_title",
fetchCallTimeWaitingLastSummary
);
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallCount_title",
createSummaryText(ReturnYouTubeDislikeApi.getFetchCallCount(),
"revanced_ryd_statistics_getFetchCallCount_zero_summary",
"revanced_ryd_statistics_getFetchCallCount_non_zero_summary"
)
);
addStatisticPreference(
"revanced_ryd_statistics_getFetchCallNumberOfFailures_title",
createSummaryText(ReturnYouTubeDislikeApi.getFetchCallNumberOfFailures(),
"revanced_ryd_statistics_getFetchCallNumberOfFailures_zero_summary",
"revanced_ryd_statistics_getFetchCallNumberOfFailures_non_zero_summary"
)
);
addStatisticPreference(
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_title",
createSummaryText(ReturnYouTubeDislikeApi.getNumberOfRateLimitRequestsEncountered(),
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_zero_summary",
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"
)
);
} catch (Exception ex) {
Logger.printException(() -> "onAttachedToActivity failure", ex);
}
}
private void addStatisticPreference(String titleKey, String SummaryText) {
Preference statisticPreference = new Preference(getContext());
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str(titleKey));
statisticPreference.setSummary(SummaryText);
addPreference(statisticPreference);
}
}

View File

@@ -11,8 +11,6 @@ import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage; import app.revanced.extension.shared.settings.AppLanguage;
@@ -21,8 +19,6 @@ import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.VersionCheckPatch; import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
import app.revanced.extension.youtube.settings.preference.ReturnYouTubeDislikePreferenceFragment;
import app.revanced.extension.youtube.settings.preference.SponsorBlockPreferenceFragment;
/** /**
* Hooks LicenseActivity. * Hooks LicenseActivity.
@@ -84,31 +80,19 @@ public class LicenseActivityHook {
public static void initialize(Activity licenseActivity) { public static void initialize(Activity licenseActivity) {
try { try {
ThemeHelper.setActivityTheme(licenseActivity); ThemeHelper.setActivityTheme(licenseActivity);
ThemeHelper.setNavigationBarColor(licenseActivity.getWindow());
licenseActivity.setContentView(getResourceIdentifier( licenseActivity.setContentView(getResourceIdentifier(
"revanced_settings_with_toolbar", "layout")); "revanced_settings_with_toolbar", "layout"));
PreferenceFragment fragment; // Sanity check.
String toolbarTitleResourceName; String dataString = licenseActivity.getIntent().getDataString();
String dataString = Objects.requireNonNull(licenseActivity.getIntent().getDataString()); if (!"revanced_settings_intent".equals(dataString)) {
switch (dataString) { Logger.printException(() -> "Unknown intent: " + dataString);
case "revanced_sb_settings_intent": return;
toolbarTitleResourceName = "revanced_sb_settings_title";
fragment = new SponsorBlockPreferenceFragment();
break;
case "revanced_ryd_settings_intent":
toolbarTitleResourceName = "revanced_ryd_settings_title";
fragment = new ReturnYouTubeDislikePreferenceFragment();
break;
case "revanced_settings_intent":
toolbarTitleResourceName = "revanced_settings_title";
fragment = new ReVancedPreferenceFragment();
break;
default:
Logger.printException(() -> "Unknown setting: " + dataString);
return;
} }
createToolbar(licenseActivity, toolbarTitleResourceName); PreferenceFragment fragment = new ReVancedPreferenceFragment();
createToolbar(licenseActivity, fragment);
//noinspection deprecation //noinspection deprecation
licenseActivity.getFragmentManager() licenseActivity.getFragmentManager()
@@ -121,20 +105,19 @@ public class LicenseActivityHook {
} }
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
private static void createToolbar(Activity activity, String toolbarTitleResourceName) { private static void createToolbar(Activity activity, PreferenceFragment fragment) {
// Replace dummy placeholder toolbar. // Replace dummy placeholder toolbar.
// This is required to fix submenu title alignment issue with Android ASOP 15+ // This is required to fix submenu title alignment issue with Android ASOP 15+
ViewGroup toolBarParent = activity.findViewById( ViewGroup toolBarParent = activity.findViewById(
getResourceIdentifier("revanced_toolbar_parent", "id")); getResourceIdentifier("revanced_toolbar_parent", "id"));
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent,"revanced_toolbar"); ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
toolbarLayoutParams = dummyToolbar.getLayoutParams(); toolbarLayoutParams = dummyToolbar.getLayoutParams();
toolBarParent.removeView(dummyToolbar); toolBarParent.removeView(dummyToolbar);
Toolbar toolbar = new Toolbar(toolBarParent.getContext()); Toolbar toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(ThemeHelper.getToolbarBackgroundColor()); toolbar.setBackgroundColor(ThemeHelper.getToolbarBackgroundColor());
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable()); toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> activity.onBackPressed()); toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
toolbar.setTitle(getResourceIdentifier(toolbarTitleResourceName, "string"));
final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16,
Utils.getContext().getResources().getDisplayMetrics()); Utils.getContext().getResources().getDisplayMetrics());
@@ -147,7 +130,11 @@ public class LicenseActivityHook {
} }
setToolbarLayoutParams(toolbar); setToolbarLayoutParams(toolbar);
// Add Search Icon and EditText for ReVancedPreferenceFragment only.
if (fragment instanceof ReVancedPreferenceFragment) {
SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
}
toolBarParent.addView(toolbar, 0); toolBarParent.addView(toolbar, 0);
} }
} }

View File

@@ -0,0 +1,381 @@
package app.revanced.extension.youtube.settings;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.graphics.drawable.GradientDrawable;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.SearchView;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
/**
* Controller for managing the search view in ReVanced settings.
*/
@SuppressWarnings({"deprecated", "DiscouragedApi"})
public class SearchViewController {
private static final int MAX_HISTORY_SIZE = 5;
private final SearchView searchView;
private final FrameLayout searchContainer;
private final Toolbar toolbar;
private final Activity activity;
private boolean isSearchActive;
private final CharSequence originalTitle;
private final Deque<String> searchHistory;
private final AutoCompleteTextView autoCompleteTextView;
private final boolean showSettingsSearchHistory;
/**
* Creates a background drawable for the SearchView with rounded corners.
*/
private static GradientDrawable createBackgroundDrawable(Context context) {
GradientDrawable background = new GradientDrawable();
background.setShape(GradientDrawable.RECTANGLE);
background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius.
int baseColor = ThemeHelper.getBackgroundColor();
int adjustedColor = ThemeHelper.isDarkTheme()
? ThemeHelper.adjustColorBrightness(baseColor, 1.11f) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
background.setColor(adjustedColor);
return background;
}
/**
* Creates a background drawable for suggestion items with rounded corners.
*/
private static GradientDrawable createSuggestionBackgroundDrawable(Context context) {
GradientDrawable background = new GradientDrawable();
background.setShape(GradientDrawable.RECTANGLE);
background.setCornerRadius(8 * context.getResources().getDisplayMetrics().density); // 8dp corner radius.
return background;
}
/**
* Adds search view components to the activity.
*/
public static void addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
new SearchViewController(activity, toolbar, fragment);
}
private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
this.activity = activity;
this.toolbar = toolbar;
this.originalTitle = toolbar.getTitle();
this.showSettingsSearchHistory = Settings.SETTINGS_SEARCH_HISTORY.get();
this.searchHistory = new LinkedList<>();
StringSetting searchEntries = Settings.SETTINGS_SEARCH_ENTRIES;
if (showSettingsSearchHistory) {
String entries = searchEntries.get();
if (!entries.isBlank()) {
searchHistory.addAll(Arrays.asList(entries.split("\n")));
}
} else {
// Clear old saved history if the user turns off the feature.
searchEntries.resetToDefault();
}
// Retrieve SearchView and container from XML.
searchView = activity.findViewById(getResourceIdentifier(
"revanced_search_view", "id"));
searchContainer = activity.findViewById(getResourceIdentifier(
"revanced_search_view_container", "id"));
// Initialize AutoCompleteTextView.
autoCompleteTextView = searchView.findViewById(
searchView.getContext().getResources().getIdentifier(
"android:id/search_src_text", null, null));
// Set background and query hint.
searchView.setBackground(createBackgroundDrawable(toolbar.getContext()));
searchView.setQueryHint(str("revanced_settings_search_hint"));
// Configure RTL support based on app language.
AppLanguage appLanguage = BaseSettings.REVANCED_LANGUAGE.get();
if (Utils.isRightToLeftLocale(appLanguage.getLocale())) {
searchView.setTextDirection(View.TEXT_DIRECTION_RTL);
searchView.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
}
// Set up search history suggestions.
if (showSettingsSearchHistory) {
setupSearchHistory();
}
// Set up query text listener.
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
try {
String queryTrimmed = query.trim();
if (!queryTrimmed.isEmpty()) {
saveSearchQuery(queryTrimmed);
}
// Hide suggestions on submit.
if (showSettingsSearchHistory && autoCompleteTextView != null) {
autoCompleteTextView.dismissDropDown();
}
} catch (Exception ex) {
Logger.printException(() -> "onQueryTextSubmit failure", ex);
}
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
try {
Logger.printDebug(() -> "Search query: " + newText);
fragment.filterPreferences(newText);
// Prevent suggestions from showing during text input.
if (showSettingsSearchHistory && autoCompleteTextView != null) {
if (!newText.isEmpty()) {
autoCompleteTextView.dismissDropDown();
autoCompleteTextView.setThreshold(Integer.MAX_VALUE); // Disable autocomplete suggestions.
} else {
autoCompleteTextView.setThreshold(1); // Re-enable for empty input.
}
}
} catch (Exception ex) {
Logger.printException(() -> "onQueryTextChange failure", ex);
}
return true;
}
});
// Set menu and search icon.
final int actionSearchId = getResourceIdentifier("action_search", "id");
toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu"));
MenuItem searchItem = toolbar.getMenu().findItem(actionSearchId);
searchItem.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_search_white_24"
: "yt_outline_search_black_24",
"drawable")).setTooltipText(null);
// Set menu item click listener.
toolbar.setOnMenuItemClickListener(item -> {
try {
if (item.getItemId() == actionSearchId) {
if (!isSearchActive) {
openSearch();
}
return true;
}
} catch (Exception ex) {
Logger.printException(() -> "menu click failure", ex);
}
return false;
});
// Set navigation click listener.
toolbar.setNavigationOnClickListener(view -> {
try {
if (isSearchActive) {
closeSearch();
} else {
activity.onBackPressed();
}
} catch (Exception ex) {
Logger.printException(() -> "navigation click failure", ex);
}
});
}
/**
* Sets up the search history suggestions for the SearchView with custom adapter.
*/
private void setupSearchHistory() {
if (autoCompleteTextView != null) {
SearchHistoryAdapter adapter = new SearchHistoryAdapter(activity, new ArrayList<>(searchHistory));
autoCompleteTextView.setAdapter(adapter);
autoCompleteTextView.setThreshold(1); // Initial threshold for empty input.
autoCompleteTextView.setLongClickable(true);
// Show suggestions only when search bar is active and query is empty.
autoCompleteTextView.setOnFocusChangeListener((v, hasFocus) -> {
if (hasFocus && isSearchActive && autoCompleteTextView.getText().length() == 0) {
autoCompleteTextView.showDropDown();
}
});
}
}
/**
* Saves a search query to the search history.
* @param query The search query to save.
*/
private void saveSearchQuery(String query) {
if (!showSettingsSearchHistory) {
return;
}
searchHistory.remove(query); // Remove if already exists to update position.
searchHistory.addFirst(query); // Add to the most recent.
// Remove extra old entries.
while (searchHistory.size() > MAX_HISTORY_SIZE) {
String last = searchHistory.removeLast();
Logger.printDebug(() -> "Removing search history query: " + last);
}
saveSearchHistory();
updateSearchHistoryAdapter();
}
/**
* Removes a search query from the search history.
* @param query The search query to remove.
*/
private void removeSearchQuery(String query) {
searchHistory.remove(query);
saveSearchHistory();
updateSearchHistoryAdapter();
}
/**
* Save the search history to the shared preferences.
*/
private void saveSearchHistory() {
Logger.printDebug(() -> "Saving search history: " + searchHistory);
Settings.SETTINGS_SEARCH_ENTRIES.save(
String.join("\n", searchHistory)
);
}
/**
* Updates the search history adapter with the latest history.
*/
private void updateSearchHistoryAdapter() {
if (autoCompleteTextView == null) {
return;
}
SearchHistoryAdapter adapter = (SearchHistoryAdapter) autoCompleteTextView.getAdapter();
if (adapter != null) {
adapter.clear();
adapter.addAll(searchHistory);
adapter.notifyDataSetChanged();
}
}
/**
* Opens the search view and shows the keyboard.
*/
private void openSearch() {
isSearchActive = true;
toolbar.getMenu().findItem(getResourceIdentifier(
"action_search", "id")).setVisible(false);
toolbar.setTitle("");
searchContainer.setVisibility(View.VISIBLE);
searchView.requestFocus();
// Show keyboard.
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(searchView, InputMethodManager.SHOW_IMPLICIT);
// Show suggestions with a slight delay.
if (showSettingsSearchHistory && autoCompleteTextView != null && autoCompleteTextView.getText().length() == 0) {
searchView.postDelayed(() -> {
if (isSearchActive && autoCompleteTextView.getText().length() == 0) {
autoCompleteTextView.showDropDown();
}
}, 100); // 100ms delay to ensure focus is stable.
}
}
/**
* Closes the search view and hides the keyboard.
*/
private void closeSearch() {
isSearchActive = false;
toolbar.getMenu().findItem(getResourceIdentifier(
"action_search", "id"))
.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_search_white_24"
: "yt_outline_search_black_24",
"drawable")
).setVisible(true);
toolbar.setTitle(originalTitle);
searchContainer.setVisibility(View.GONE);
searchView.setQuery("", false);
// Hide keyboard.
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
}
/**
* Custom ArrayAdapter for search history.
*/
private class SearchHistoryAdapter extends ArrayAdapter<String> {
public SearchHistoryAdapter(Context context, List<String> history) {
super(context, 0, history);
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull android.view.ViewGroup parent) {
if (convertView == null) {
convertView = LinearLayout.inflate(getContext(), getResourceIdentifier(
"revanced_search_suggestion_item", "layout"), null);
}
// Apply rounded corners programmatically.
convertView.setBackground(createSuggestionBackgroundDrawable(getContext()));
String query = getItem(position);
// Set query text.
TextView textView = convertView.findViewById(getResourceIdentifier(
"suggestion_text", "id"));
if (textView != null) {
textView.setText(query);
}
// Set click listener for inserting query into SearchView.
convertView.setOnClickListener(v -> {
searchView.setQuery(query, true); // Insert selected query and submit.
});
// Set long click listener for deletion confirmation.
convertView.setOnLongClickListener(v -> {
new AlertDialog.Builder(activity)
.setTitle(query)
.setMessage(str("revanced_settings_search_remove_message"))
.setPositiveButton(android.R.string.ok,
(dialog, which) -> removeSearchQuery(query))
.setNegativeButton(android.R.string.cancel, null)
.show();
return true;
});
return convertView;
}
}
}

View File

@@ -3,15 +3,14 @@ package app.revanced.extension.youtube.settings;
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 static app.revanced.extension.shared.settings.Setting.Availability; import static app.revanced.extension.shared.settings.Setting.Availability;
import static app.revanced.extension.shared.settings.Setting.migrateFromOldPreferences;
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew; import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
import static app.revanced.extension.shared.settings.Setting.parent; import static app.revanced.extension.shared.settings.Setting.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;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MINIMAL; import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MINIMAL;
@@ -21,12 +20,15 @@ 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.VersionCheckPatch.IS_19_17_OR_GREATER; 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.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
import android.graphics.Color; import android.graphics.Color;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
@@ -38,23 +40,23 @@ import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.shared.settings.LongSetting; import app.revanced.extension.shared.settings.LongSetting;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting; import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
import app.revanced.extension.youtube.patches.MiniplayerPatch;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
public class Settings extends BaseSettings { public class Settings extends BaseSettings {
// Video // Video
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2); public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2); public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE); public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_shorts_quality_default_wifi", -2, true); public static final IntegerSetting SHORTS_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_shorts_quality_default_wifi", -2, true);
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_shorts_quality_default_mobile", -2, true); public static final IntegerSetting SHORTS_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_shorts_quality_default_mobile", -2, true);
public static final BooleanSetting REMEMBER_SHORTS_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_shorts_quality_last_selected", FALSE); public static final BooleanSetting REMEMBER_SHORTS_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_shorts_quality_last_selected", FALSE);
public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE); public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
// Speed // Speed
public static final FloatSetting SPEED_TAP_AND_HOLD = new FloatSetting("revanced_speed_tap_and_hold", 2.0f, true); public static final FloatSetting SPEED_TAP_AND_HOLD = new FloatSetting("revanced_speed_tap_and_hold", 2.0f, true);
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE); public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE);
@@ -103,7 +105,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE); public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE); public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE); public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATIONS = new BooleanSetting("revanced_hide_search_result_recommendations", TRUE); public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_search_result_recommendation_labels", TRUE);
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true); public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
// Alternative thumbnails // Alternative thumbnails
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL); public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
@@ -126,6 +128,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);
@@ -138,6 +141,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE); public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE); public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true); public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true);
public static final BooleanSetting HIDE_RELATED_VIDEO_OVERLAY = new BooleanSetting("revanced_hide_related_video_overlay", FALSE, true);
public static final BooleanSetting HIDE_HIDE_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_channel_guidelines", TRUE); public static final BooleanSetting HIDE_HIDE_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_channel_guidelines", TRUE);
public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE); public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE); public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
@@ -159,7 +163,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN); public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN); public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN);
public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerHorizontalDragAvailability()); public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerHorizontalDragAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true, new MiniplayerHideExpandCloseAvailability()); public static final BooleanSetting MINIPLAYER_HIDE_OVERLAY_BUTTONS = new BooleanSetting("revanced_miniplayer_hide_overlay_buttons", FALSE, true, new MiniplayerPatch.MiniplayerHideOverlayButtonsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3)); public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3));
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, MINIPLAYER_TYPE.availability(MODERN_1)); public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, MINIPLAYER_TYPE.availability(MODERN_1));
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN); public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
@@ -171,7 +175,8 @@ public class Settings extends BaseSettings {
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name", public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON)); "org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
// Comments // Comments
public static final BooleanSetting HIDE_COMMENTS_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_chat_summary", FALSE); public static final BooleanSetting HIDE_COMMENTS_AI_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_chat_summary", FALSE);
public static final BooleanSetting HIDE_COMMENTS_AI_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_summary", FALSE);
public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE); public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE);
public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE); public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE);
public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comments_timestamp_and_emoji_buttons", TRUE); public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comments_timestamp_and_emoji_buttons", TRUE);
@@ -179,6 +184,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE); public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE);
public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE); public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
// Description // Description
public static final BooleanSetting HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION = new BooleanSetting("revanced_hide_ai_generated_video_summary_section", FALSE);
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE); public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE); public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE); public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE);
@@ -196,10 +202,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);
@@ -213,6 +220,8 @@ public class Settings extends BaseSettings {
// General layout // General layout
public static final BooleanSetting RESTORE_OLD_SETTINGS_MENUS = new BooleanSetting("revanced_restore_old_settings_menus", FALSE, true); public static final BooleanSetting RESTORE_OLD_SETTINGS_MENUS = new BooleanSetting("revanced_restore_old_settings_menus", FALSE, true);
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "", true);
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message"); public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true); public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true); public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
@@ -221,7 +230,9 @@ 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 StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", IS_19_17_OR_GREATER ? "19.26.42" : "17.33.42", true, parent(SPOOF_APP_VERSION)); 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));
// 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);
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER)); public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
@@ -234,7 +245,8 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true); public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true);
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true, public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true,
"revanced_switch_create_with_notifications_button_user_dialog_message"); "revanced_switch_create_with_notifications_button_user_dialog_message");
public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", FALSE, true); public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", FALSE, true,
"revanced_disable_translucent_status_bar_user_dialog_message");
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true); public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true);
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK = new BooleanSetting("revanced_disable_translucent_navigation_bar_dark", FALSE, true); public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK = new BooleanSetting("revanced_disable_translucent_navigation_bar_dark", FALSE, true);
@@ -293,7 +305,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 +329,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));
@@ -331,19 +345,17 @@ public class Settings extends BaseSettings {
public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true, parent(SWIPE_BRIGHTNESS)); public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true, parent(SWIPE_BRIGHTNESS));
// ReturnYoutubeDislike // ReturnYoutubeDislike
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("ryd_enabled", TRUE); public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);
public static final StringSetting RYD_USER_ID = new StringSetting("ryd_user_id", "", false, false); public static final StringSetting RYD_USER_ID = new StringSetting("revanced_ryd_user_id", "", false, false);
public static final BooleanSetting RYD_SHORTS = new BooleanSetting("ryd_shorts", TRUE, parent(RYD_ENABLED)); public static final BooleanSetting RYD_SHORTS = new BooleanSetting("revanced_ryd_shorts", TRUE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("ryd_dislike_percentage", FALSE, parent(RYD_ENABLED)); public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("revanced_ryd_dislike_percentage", FALSE, true, parent(RYD_ENABLED));
public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("ryd_compact_layout", FALSE, parent(RYD_ENABLED)); public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("revanced_ryd_compact_layout", FALSE, true, parent(RYD_ENABLED));
public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("ryd_estimated_like", TRUE, parent(RYD_ENABLED)); public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("revanced_ryd_estimated_like", TRUE, true, parent(RYD_ENABLED));
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED)); public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("revanced_ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED));
// SponsorBlock // SponsorBlock
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE); public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
/** /** Do not use id setting directly. Instead use {@link SponsorBlockSettings}. */
* Do not use directly, instead use {@link SponsorBlockSettings}
*/
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", ""); public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "");
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED)); public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED)); public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
@@ -365,51 +377,52 @@ public class Settings extends BaseSettings {
public static final BooleanSetting SB_SEEN_GUIDELINES = new BooleanSetting("sb_seen_guidelines", FALSE, false, false); public static final BooleanSetting SB_SEEN_GUIDELINES = new BooleanSetting("sb_seen_guidelines", FALSE, false, false);
public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color", "#00D400"); public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color", "#00D400");
public static final FloatSetting SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#FFFF00"); public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#FFFF00");
public static final FloatSetting SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CC00FF"); public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CC00FF");
public static final FloatSetting SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684"); public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684");
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF"); public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#0202ED"); public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#0202ED");
public static final FloatSetting SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", IGNORE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", IGNORE.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#008FD6"); public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#008FD6");
public static final FloatSetting SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", IGNORE.reVancedKeyValue); public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", IGNORE.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#7300FF"); public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#7300FF");
public static final FloatSetting SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", MANUAL_SKIP.reVancedKeyValue); public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", MANUAL_SKIP.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#FF9900"); public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#FF9900");
public static final FloatSetting SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f);
public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue); public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue);
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF"); public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF");
public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f);
// Deprecated migrations // Deprecated migrations
private static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE, true); private static final BooleanSetting DEPRECATED_HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE, true);
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", FALSE); private static final BooleanSetting DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", FALSE);
private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127); private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127);
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033"); private static final 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_AUTO_CAPTIONS = new BooleanSetting("revanced_auto_captions", FALSE);
static { static {
// region Migration // region Migration
// Do _not_ delete this SB private user id migration property until sometime in early 2025.
// This is the only setting that cannot be reconfigured if lost,
// and more time should be given for users who rarely upgrade.
SharedPrefCategory sbPrefs = new SharedPrefCategory("sponsor-block");
// Remove the "sb_" prefix, as old settings are saved without it.
String key = DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING.key.substring(3);
migrateFromOldPreferences(sbPrefs, DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, key);
migrateOldSettingToNew(DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID);
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_BUTTONS, HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS); migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_BUTTONS, HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS);
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER); migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
migrateOldSettingToNew(DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN, HIDE_END_SCREEN_SUGGESTED_VIDEO); migrateOldSettingToNew(DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN, HIDE_END_SCREEN_SUGGESTED_VIDEO);
migrateOldSettingToNew(DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU, ADVANCED_VIDEO_QUALITY_MENU);
migrateOldSettingToNew(DEPRECATED_AUTO_CAPTIONS, DISABLE_AUTO_CAPTIONS);
// Migrate renamed enum. // Migrate renamed enum.
//noinspection deprecation //noinspection deprecation
@@ -446,6 +459,22 @@ public class Settings extends BaseSettings {
DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.resetToDefault(); DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.resetToDefault();
} }
// Old spoof versions that no longer work.
if (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) {
Logger.printInfo(() -> "Resetting spoof app version target");
SPOOF_APP_VERSION_TARGET.resetToDefault();
}
// RYD requires manually migrating old settings since the lack of
// a "revanced_" on the old setting causes duplicate key exceptions during export.
SharedPrefCategory revancedPrefs = Setting.preferences;
Setting.migrateFromOldPreferences(revancedPrefs, RYD_USER_ID, "ryd_user_id");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_ENABLED, "ryd_enabled");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_DISLIKE_PERCENTAGE, "ryd_dislike_percentage");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_COMPACT_LAYOUT, "ryd_compact_layout");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_ESTIMATED_LIKE, "ryd_estimated_like");
Setting.migrateFromOldPreferences(revancedPrefs, RYD_TOAST_ON_CONNECTION_ERROR, "ryd_toast_on_connection_error");
// endregion // endregion
// region SB import/export callbacks // region SB import/export callbacks
@@ -455,4 +484,3 @@ public class Settings extends BaseSettings {
// endregion // endregion
} }
} }

View File

@@ -1,23 +1,15 @@
package app.revanced.extension.youtube.settings.preference; package app.revanced.extension.youtube.settings.preference;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.preference.Preference;
import android.util.AttributeSet; import android.util.AttributeSet;
/** /**
* Allows tapping the DeArrow about preference to open the DeArrow website. * Allows tapping the DeArrow about preference to open the DeArrow website.
*/ */
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings("unused")
public class AlternativeThumbnailsAboutDeArrowPreference extends Preference { public class AlternativeThumbnailsAboutDeArrowPreference extends UrlLinkPreference {
{ {
setOnPreferenceClickListener(pref -> { externalUrl = "https://dearrow.ajay.app";
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://dearrow.ajay.app"));
pref.getContext().startActivity(i);
return false;
});
} }
public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

View File

@@ -0,0 +1,62 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.sf;
import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings;
/**
* Custom video speeds used by {@link CustomPlaybackSpeedPatch}.
*/
@SuppressWarnings({"unused", "deprecation"})
public final class CustomVideoSpeedListPreference extends ListPreference {
/**
* Initialize a settings preference list with the available playback speeds.
*/
private void initializeEntryValues() {
float[] customPlaybackSpeeds = CustomPlaybackSpeedPatch.customPlaybackSpeeds;
final int numberOfEntries = customPlaybackSpeeds.length + 1;
String[] preferenceListEntries = new String[numberOfEntries];
String[] preferenceListEntryValues = new String[numberOfEntries];
// Auto speed (same behavior as unpatched).
preferenceListEntries[0] = sf("revanced_custom_playback_speeds_auto").toString();
preferenceListEntryValues[0] = String.valueOf(Settings.PLAYBACK_SPEED_DEFAULT.defaultValue);
int i = 1;
for (float speed : customPlaybackSpeeds) {
String speedString = String.valueOf(speed);
preferenceListEntries[i] = speedString + "x";
preferenceListEntryValues[i] = speedString;
i++;
}
setEntries(preferenceListEntries);
setEntryValues(preferenceListEntryValues);
}
{
initializeEntryValues();
}
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomVideoSpeedListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomVideoSpeedListPreference(Context context) {
super(context);
}
}

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

@@ -19,12 +19,15 @@ public class HtmlPreference extends Preference {
public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
} }
public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr) { public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
} }
public HtmlPreference(Context context, AttributeSet attrs) { public HtmlPreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
} }
public HtmlPreference(Context context) { public HtmlPreference(Context context) {
super(context); super(context);
} }

View File

@@ -1,5 +1,6 @@
package app.revanced.extension.youtube.settings.preference; package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier; import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -9,35 +10,66 @@ import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.util.Pair; import android.preference.SwitchPreference;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Deque;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.EnumSetting;
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment; import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
import app.revanced.extension.youtube.ThemeHelper; import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.LicenseActivityHook; import app.revanced.extension.youtube.settings.LicenseActivityHook;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
/** /**
* Preference fragment for ReVanced settings. * Preference fragment for ReVanced settings.
*
* @noinspection deprecation
*/ */
@SuppressWarnings("deprecation")
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment { public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
/**
* The main PreferenceScreen used to display the current set of preferences.
* This screen is manipulated during initialization and filtering to show or hide preferences.
*/
private PreferenceScreen preferenceScreen;
/**
* A copy of the original PreferenceScreen created during initialization.
* Used to restore the preference structure to its initial state after filtering or other modifications.
*/
private PreferenceScreen originalPreferenceScreen;
/**
* Used for searching preferences. A Collection of all preferences including nested preferences.
* Root preferences are excluded (no need to search what's on the root screen),
* but their sub preferences are included.
*/
private final List<AbstractPreferenceSearchData<?>> allPreferences = new ArrayList<>();
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() { public static Drawable getBackButtonDrawable() {
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme() final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
@@ -48,83 +80,140 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
} }
/** /**
* Sorts a preference list by menu entries, but preserves the first value as the first entry. * Initializes the preference fragment, copying the original screen to allow full restoration.
*
* @noinspection SameParameterValue
*/ */
private static void sortListPreferenceByValues(ListPreference listPreference, int firstEntriesToPreserve) {
CharSequence[] entries = listPreference.getEntries();
CharSequence[] entryValues = listPreference.getEntryValues();
final int entrySize = entries.length;
if (entrySize != entryValues.length) {
// Xml array declaration has a missing/extra entry.
throw new IllegalStateException();
}
List<Pair<String, String>> firstPairs = new ArrayList<>(firstEntriesToPreserve);
List<Pair<String, String>> pairsToSort = new ArrayList<>(entrySize);
for (int i = 0; i < entrySize; i++) {
Pair<String, String> pair = new Pair<>(entries[i].toString(), entryValues[i].toString());
if (i < firstEntriesToPreserve) {
firstPairs.add(pair);
} else {
pairsToSort.add(pair);
}
}
Collections.sort(pairsToSort, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
CharSequence[] sortedEntries = new CharSequence[entrySize];
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
int i = 0;
for (Pair<String, String> pair : firstPairs) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
for (Pair<String, String> pair : pairsToSort) {
sortedEntries[i] = pair.first;
sortedEntryValues[i] = pair.second;
i++;
}
listPreference.setEntries(sortedEntries);
listPreference.setEntryValues(sortedEntryValues);
}
@Override @Override
protected void initialize() { protected void initialize() {
super.initialize(); super.initialize();
try { try {
setPreferenceScreenToolbar(getPreferenceScreen()); preferenceScreen = getPreferenceScreen();
Utils.sortPreferenceGroups(preferenceScreen);
// If the preference was included, then initialize it based on the available playback speed. // Store the original structure for restoration after filtering.
Preference preference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key); originalPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
if (preference instanceof ListPreference playbackPreference) { for (int i = 0, count = preferenceScreen.getPreferenceCount(); i < count; i++) {
CustomPlaybackSpeedPatch.initializeListPreference(playbackPreference); originalPreferenceScreen.addPreference(preferenceScreen.getPreference(i));
} }
sortPreferenceListMenu(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE); setPreferenceScreenToolbar(preferenceScreen);
sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex); Logger.printException(() -> "initialize failure", ex);
} }
} }
private void sortPreferenceListMenu(EnumSetting<?> setting) { /**
Preference preference = findPreference(setting.key); * Called when the fragment starts, ensuring all preferences are collected after initialization.
if (preference instanceof ListPreference languagePreference) { */
sortListPreferenceByValues(languagePreference, 1); @Override
public void onStart() {
super.onStart();
try {
if (allPreferences.isEmpty()) {
// Must collect preferences on start and not in initialize since
// legacy SB settings are not loaded yet.
Logger.printDebug(() -> "Collecting preferences to search");
// Do not show root menu preferences in search results.
// Instead search for everything that's not shown when search is not active.
collectPreferences(preferenceScreen, 1, 0);
}
} catch (Exception ex) {
Logger.printException(() -> "onStart failure", ex);
} }
} }
/**
* Recursively collects all preferences from the screen or group.
* @param includeDepth Menu depth to start including preferences.
* A value of 0 adds all preferences.
*/
private void collectPreferences(PreferenceGroup group, int includeDepth, int currentDepth) {
for (int i = 0, count = group.getPreferenceCount(); i < count; i++) {
Preference preference = group.getPreference(i);
if (includeDepth <= currentDepth && !(preference instanceof PreferenceCategory)
&& !(preference instanceof SponsorBlockPreferenceGroup)) {
AbstractPreferenceSearchData<?> data;
if (preference instanceof SwitchPreference switchPref) {
data = new SwitchPreferenceSearchData(switchPref);
} else if (preference instanceof ListPreference listPref) {
data = new ListPreferenceSearchData(listPref);
} else {
data = new PreferenceSearchData(preference);
}
allPreferences.add(data);
}
if (preference instanceof PreferenceGroup subGroup) {
collectPreferences(subGroup, includeDepth, currentDepth + 1);
}
}
}
/**
* Filters the preferences using the given query string and applies highlighting.
*/
public void filterPreferences(String query) {
preferenceScreen.removeAll();
if (TextUtils.isEmpty(query)) {
// Restore original preferences and their titles/summaries/entries.
for (int i = 0, count = originalPreferenceScreen.getPreferenceCount(); i < count; i++) {
preferenceScreen.addPreference(originalPreferenceScreen.getPreference(i));
}
for (AbstractPreferenceSearchData<?> data : allPreferences) {
data.clearHighlighting();
}
return;
}
// Navigation path -> Category
Map<String, PreferenceCategory> categoryMap = new HashMap<>();
String queryLower = Utils.removePunctuationToLowercase(query);
Pattern queryPattern = Pattern.compile(Pattern.quote(Utils.removePunctuationToLowercase(query)),
Pattern.CASE_INSENSITIVE);
for (AbstractPreferenceSearchData<?> data : allPreferences) {
if (data.matchesSearchQuery(queryLower)) {
data.applyHighlighting(queryLower, queryPattern);
String navigationPath = data.navigationPath;
PreferenceCategory group = categoryMap.computeIfAbsent(navigationPath, key -> {
PreferenceCategory newGroup = new PreferenceCategory(preferenceScreen.getContext());
newGroup.setTitle(navigationPath);
preferenceScreen.addPreference(newGroup);
return newGroup;
});
group.addPreference(data.preference);
}
}
// Show 'No results found' if search results are empty.
if (categoryMap.isEmpty()) {
Preference noResultsPreference = new Preference(preferenceScreen.getContext());
noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query));
noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary"));
noResultsPreference.setSelectable(false);
// Set icon for the placeholder preference.
noResultsPreference.setLayoutResource(getResourceIdentifier(
"revanced_preference_with_icon_no_search_result", "layout"));
noResultsPreference.setIcon(getResourceIdentifier(
ThemeHelper.isDarkTheme() ? "yt_outline_search_white_24" : "yt_outline_search_black_24",
"drawable"));
preferenceScreen.addPreference(noResultsPreference);
}
}
/**
* Sets toolbar for all nested preference screens.
*/
private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) { private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
for (int i = 0, preferenceCount = parentScreen.getPreferenceCount(); i < preferenceCount; i++) { for (int i = 0, count = parentScreen.getPreferenceCount(); i < count; i++) {
Preference childPreference = parentScreen.getPreference(i); Preference childPreference = parentScreen.getPreference(i);
if (childPreference instanceof PreferenceScreen) { if (childPreference instanceof PreferenceScreen) {
// Recursively set sub preferences. // Recursively set sub preferences.
@@ -137,11 +226,16 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
.findViewById(android.R.id.content) .findViewById(android.R.id.content)
.getParent(); .getParent();
// Fix required for Android 15 and YT 19.45+ // Fix the system navigation bar color for submenus.
ThemeHelper.setNavigationBarColor(preferenceScreenDialog.getWindow());
// Fix edge-to-edge screen with Android 15 and YT 19.45+
// https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
rootView.setOnApplyWindowInsetsListener((v, insets) -> { rootView.setOnApplyWindowInsetsListener((v, insets) -> {
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars()); Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
v.setPadding(0, statusInsets.top, 0, 0); Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
v.setPadding(0, statusInsets.top, 0, navInsets.bottom);
return insets; return insets;
}); });
} }
@@ -150,6 +244,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
toolbar.setTitle(childScreen.getTitle()); toolbar.setTitle(childScreen.getTitle());
toolbar.setNavigationIcon(getBackButtonDrawable()); toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss()); toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
final int margin = (int) TypedValue.applyDimension( final int margin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics() TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
); );
@@ -171,3 +266,271 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
} }
} }
} }
@SuppressWarnings("deprecation")
class AbstractPreferenceSearchData<T extends Preference> {
/**
* @return The navigation path for the given preference, such as "Player > Action buttons".
*/
private static String getPreferenceNavigationString(Preference preference) {
Deque<CharSequence> pathElements = new ArrayDeque<>();
while (true) {
preference = preference.getParent();
if (preference == null) {
if (pathElements.isEmpty()) {
return "";
}
Locale locale = BaseSettings.REVANCED_LANGUAGE.get().getLocale();
return Utils.getTextDirectionString(locale) + String.join(" > ", pathElements);
}
if (!(preference instanceof NoTitlePreferenceCategory)
&& !(preference instanceof SponsorBlockPreferenceGroup)) {
CharSequence title = preference.getTitle();
if (title != null && title.length() > 0) {
pathElements.addFirst(title);
}
}
}
}
/**
* Highlights the search query in the given text by applying color span.
* @param text The original text to process.
* @param queryPattern The search query to highlight.
* @return The text with highlighted query matches as a SpannableStringBuilder.
*/
static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
if (TextUtils.isEmpty(text)) {
return text;
}
final int baseColor = ThemeHelper.getBackgroundColor();
final int adjustedColor = ThemeHelper.isDarkTheme()
? ThemeHelper.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor);
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
Matcher matcher = queryPattern.matcher(text);
while (matcher.find()) {
spannable.setSpan(
highlightSpan,
matcher.start(),
matcher.end(),
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE
);
}
return spannable;
}
final T preference;
final String key;
final String navigationPath;
boolean highlightingApplied;
@Nullable
CharSequence originalTitle;
@Nullable
String searchTitle;
AbstractPreferenceSearchData(T pref) {
preference = pref;
key = Utils.removePunctuationToLowercase(pref.getKey());
navigationPath = getPreferenceNavigationString(pref);
}
@CallSuper
void updateSearchDataIfNeeded() {
if (highlightingApplied) {
// Must clear, otherwise old highlighting is still applied.
clearHighlighting();
}
CharSequence title = preference.getTitle();
if (originalTitle != title) { // Check using reference equality.
originalTitle = title;
searchTitle = Utils.removePunctuationToLowercase(title);
}
}
@CallSuper
boolean matchesSearchQuery(String query) {
updateSearchDataIfNeeded();
return key.contains(query)
|| searchTitle != null && searchTitle.contains(query);
}
@CallSuper
void applyHighlighting(String query, Pattern queryPattern) {
preference.setTitle(highlightSearchQuery(originalTitle, queryPattern));
highlightingApplied = true;
}
@CallSuper
void clearHighlighting() {
if (highlightingApplied) {
preference.setTitle(originalTitle);
highlightingApplied = false;
}
}
}
/**
* Regular preference type that only uses the base preference summary.
* Should only be used if a more specific data class does not exist.
*/
@SuppressWarnings("deprecation")
class PreferenceSearchData extends AbstractPreferenceSearchData<Preference> {
@Nullable
CharSequence originalSummary;
@Nullable
String searchSummary;
PreferenceSearchData(Preference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence summary = preference.getSummary();
if (originalSummary != summary) {
originalSummary = summary;
searchSummary = Utils.removePunctuationToLowercase(summary);
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchSummary != null && searchSummary.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
preference.setSummary(highlightSearchQuery(originalSummary, queryPattern));
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setSummary(originalSummary);
}
}
/**
* Switch preference type that uses summaryOn and summaryOff.
*/
@SuppressWarnings("deprecation")
class SwitchPreferenceSearchData extends AbstractPreferenceSearchData<SwitchPreference> {
@Nullable
CharSequence originalSummaryOn, originalSummaryOff;
@Nullable
String searchSummaryOn, searchSummaryOff;
SwitchPreferenceSearchData(SwitchPreference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence summaryOn = preference.getSummaryOn();
if (originalSummaryOn != summaryOn) {
originalSummaryOn = summaryOn;
searchSummaryOn = Utils.removePunctuationToLowercase(summaryOn);
}
CharSequence summaryOff = preference.getSummaryOff();
if (originalSummaryOff != summaryOff) {
originalSummaryOff = summaryOff;
searchSummaryOff = Utils.removePunctuationToLowercase(summaryOff);
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchSummaryOn != null && searchSummaryOn.contains(query)
|| searchSummaryOff != null && searchSummaryOff.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
preference.setSummaryOn(highlightSearchQuery(originalSummaryOn, queryPattern));
preference.setSummaryOff(highlightSearchQuery(originalSummaryOff, queryPattern));
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setSummaryOn(originalSummaryOn);
preference.setSummaryOff(originalSummaryOff);
}
}
/**
* List preference type that uses entries.
*/
@SuppressWarnings("deprecation")
class ListPreferenceSearchData extends AbstractPreferenceSearchData<ListPreference> {
@Nullable
CharSequence[] originalEntries;
@Nullable
String searchEntries;
ListPreferenceSearchData(ListPreference pref) {
super(pref);
}
void updateSearchDataIfNeeded() {
super.updateSearchDataIfNeeded();
CharSequence[] entries = preference.getEntries();
if (originalEntries != entries) {
originalEntries = entries;
searchEntries = Utils.removePunctuationToLowercase(String.join(" ", entries));
}
}
boolean matchesSearchQuery(String query) {
return super.matchesSearchQuery(query)
|| searchEntries != null && searchEntries.contains(query);
}
@Override
void applyHighlighting(String query, Pattern queryPattern) {
super.applyHighlighting(query, queryPattern);
if (originalEntries != null) {
final int length = originalEntries.length;
CharSequence[] highlightedEntries = new CharSequence[length];
for (int i = 0; i < length; i++) {
highlightedEntries[i] = highlightSearchQuery(originalEntries[i], queryPattern);
// Cannot highlight the summary text, because ListPreference uses
// the toString() of the summary CharSequence which strips away all formatting.
}
preference.setEntries(highlightedEntries);
}
}
@CallSuper
void clearHighlighting() {
super.clearHighlighting();
preference.setEntries(originalEntries);
}
}

View File

@@ -1,256 +0,0 @@
package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.patches.ReturnYouTubeDislikePatch;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.extension.youtube.settings.Settings;
/** @noinspection deprecation*/
public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
/**
* If dislikes are shown on Shorts.
*/
private SwitchPreference shortsPreference;
/**
* If dislikes are shown as percentage.
*/
private SwitchPreference percentagePreference;
/**
* If segmented like/dislike button uses smaller compact layout.
*/
private SwitchPreference compactLayoutPreference;
/**
* If hidden likes are replaced with an estimated value.
*/
private SwitchPreference estimatedLikesPreference;
/**
* If segmented like/dislike button uses smaller compact layout.
*/
private SwitchPreference toastOnRYDNotAvailable;
private void updateUIState() {
shortsPreference.setEnabled(Settings.RYD_SHORTS.isAvailable());
percentagePreference.setEnabled(Settings.RYD_DISLIKE_PERCENTAGE.isAvailable());
compactLayoutPreference.setEnabled(Settings.RYD_COMPACT_LAYOUT.isAvailable());
estimatedLikesPreference.setEnabled(Settings.RYD_ESTIMATED_LIKE.isAvailable());
toastOnRYDNotAvailable.setEnabled(Settings.RYD_TOAST_ON_CONNECTION_ERROR.isAvailable());
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
Activity context = getActivity();
PreferenceManager manager = getPreferenceManager();
manager.setSharedPreferencesName(Setting.preferences.name);
PreferenceScreen preferenceScreen = manager.createPreferenceScreen(context);
setPreferenceScreen(preferenceScreen);
SwitchPreference enabledPreference = new SwitchPreference(context);
enabledPreference.setChecked(Settings.RYD_ENABLED.get());
enabledPreference.setTitle(str("revanced_ryd_enable_title"));
enabledPreference.setSummaryOn(str("revanced_ryd_enable_summary_on"));
enabledPreference.setSummaryOff(str("revanced_ryd_enable_summary_off"));
enabledPreference.setOnPreferenceChangeListener((pref, newValue) -> {
final Boolean rydIsEnabled = (Boolean) newValue;
Settings.RYD_ENABLED.save(rydIsEnabled);
ReturnYouTubeDislikePatch.onRYDStatusChange(rydIsEnabled);
updateUIState();
return true;
});
preferenceScreen.addPreference(enabledPreference);
shortsPreference = new SwitchPreference(context);
shortsPreference.setChecked(Settings.RYD_SHORTS.get());
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
String shortsSummary = ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
? str("revanced_ryd_shorts_summary_on")
: str("revanced_ryd_shorts_summary_on_disclaimer");
shortsPreference.setSummaryOn(shortsSummary);
shortsPreference.setSummaryOff(str("revanced_ryd_shorts_summary_off"));
shortsPreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_SHORTS.save((Boolean) newValue);
updateUIState();
return true;
});
preferenceScreen.addPreference(shortsPreference);
percentagePreference = new SwitchPreference(context);
percentagePreference.setChecked(Settings.RYD_DISLIKE_PERCENTAGE.get());
percentagePreference.setTitle(str("revanced_ryd_dislike_percentage_title"));
percentagePreference.setSummaryOn(str("revanced_ryd_dislike_percentage_summary_on"));
percentagePreference.setSummaryOff(str("revanced_ryd_dislike_percentage_summary_off"));
percentagePreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_DISLIKE_PERCENTAGE.save((Boolean) newValue);
ReturnYouTubeDislike.clearAllUICaches();
updateUIState();
return true;
});
preferenceScreen.addPreference(percentagePreference);
compactLayoutPreference = new SwitchPreference(context);
compactLayoutPreference.setChecked(Settings.RYD_COMPACT_LAYOUT.get());
compactLayoutPreference.setTitle(str("revanced_ryd_compact_layout_title"));
compactLayoutPreference.setSummaryOn(str("revanced_ryd_compact_layout_summary_on"));
compactLayoutPreference.setSummaryOff(str("revanced_ryd_compact_layout_summary_off"));
compactLayoutPreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_COMPACT_LAYOUT.save((Boolean) newValue);
ReturnYouTubeDislike.clearAllUICaches();
updateUIState();
return true;
});
preferenceScreen.addPreference(compactLayoutPreference);
estimatedLikesPreference = new SwitchPreference(context);
estimatedLikesPreference.setChecked(Settings.RYD_ESTIMATED_LIKE.get());
estimatedLikesPreference.setTitle(str("revanced_ryd_estimated_like_title"));
estimatedLikesPreference.setSummaryOn(str("revanced_ryd_estimated_like_summary_on"));
estimatedLikesPreference.setSummaryOff(str("revanced_ryd_estimated_like_summary_off"));
estimatedLikesPreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_ESTIMATED_LIKE.save((Boolean) newValue);
ReturnYouTubeDislike.clearAllUICaches();
updateUIState();
return true;
});
preferenceScreen.addPreference(estimatedLikesPreference);
toastOnRYDNotAvailable = new SwitchPreference(context);
toastOnRYDNotAvailable.setChecked(Settings.RYD_TOAST_ON_CONNECTION_ERROR.get());
toastOnRYDNotAvailable.setTitle(str("revanced_ryd_toast_on_connection_error_title"));
toastOnRYDNotAvailable.setSummaryOn(str("revanced_ryd_toast_on_connection_error_summary_on"));
toastOnRYDNotAvailable.setSummaryOff(str("revanced_ryd_toast_on_connection_error_summary_off"));
toastOnRYDNotAvailable.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_TOAST_ON_CONNECTION_ERROR.save((Boolean) newValue);
updateUIState();
return true;
});
preferenceScreen.addPreference(toastOnRYDNotAvailable);
updateUIState();
// About category
PreferenceCategory aboutCategory = new PreferenceCategory(context);
aboutCategory.setTitle(str("revanced_ryd_about"));
preferenceScreen.addPreference(aboutCategory);
// ReturnYouTubeDislike Website
Preference aboutWebsitePreference = new Preference(context);
aboutWebsitePreference.setTitle(str("revanced_ryd_attribution_title"));
aboutWebsitePreference.setSummary(str("revanced_ryd_attribution_summary"));
aboutWebsitePreference.setOnPreferenceClickListener(pref -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://returnyoutubedislike.com"));
pref.getContext().startActivity(i);
return false;
});
aboutCategory.addPreference(aboutWebsitePreference);
// RYD API connection statistics
if (BaseSettings.DEBUG.get()) {
PreferenceCategory emptyCategory = new PreferenceCategory(context); // vertical padding
preferenceScreen.addPreference(emptyCategory);
PreferenceCategory statisticsCategory = new PreferenceCategory(context);
statisticsCategory.setTitle(str("revanced_ryd_statistics_category_title"));
preferenceScreen.addPreference(statisticsCategory);
Preference statisticPreference;
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeAverage_title"));
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeAverage()));
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeMin_title"));
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMin()));
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeMax_title"));
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMax()));
preferenceScreen.addPreference(statisticPreference);
String fetchCallTimeWaitingLastSummary;
final long fetchCallTimeWaitingLast = ReturnYouTubeDislikeApi.getFetchCallResponseTimeLast();
if (fetchCallTimeWaitingLast == ReturnYouTubeDislikeApi.FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT) {
fetchCallTimeWaitingLastSummary = str("revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary");
} else {
fetchCallTimeWaitingLastSummary = createMillisecondStringFromNumber(fetchCallTimeWaitingLast);
}
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeLast_title"));
statisticPreference.setSummary(fetchCallTimeWaitingLastSummary);
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallCount_title"));
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getFetchCallCount(),
"revanced_ryd_statistics_getFetchCallCount_zero_summary",
"revanced_ryd_statistics_getFetchCallCount_non_zero_summary"));
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallNumberOfFailures_title"));
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getFetchCallNumberOfFailures(),
"revanced_ryd_statistics_getFetchCallNumberOfFailures_zero_summary",
"revanced_ryd_statistics_getFetchCallNumberOfFailures_non_zero_summary"));
preferenceScreen.addPreference(statisticPreference);
statisticPreference = new Preference(context);
statisticPreference.setSelectable(false);
statisticPreference.setTitle(str("revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_title"));
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getNumberOfRateLimitRequestsEncountered(),
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_zero_summary",
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"));
preferenceScreen.addPreference(statisticPreference);
}
} catch (Exception ex) {
Logger.printException(() -> "onCreate failure", ex);
}
}
private static String createSummaryText(int value, String summaryStringZeroKey, String summaryStringOneOrMoreKey) {
if (value == 0) {
return str(summaryStringZeroKey);
}
return String.format(str(summaryStringOneOrMoreKey), value);
}
private static String createMillisecondStringFromNumber(long number) {
return String.format(str("revanced_ryd_statistics_millisecond_text"), number);
}
}

View File

@@ -1,616 +0,0 @@
package app.revanced.extension.youtube.settings.preference;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.preference.*;
import android.text.Html;
import android.text.InputType;
import android.util.TypedValue;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategoryListPreference;
import app.revanced.extension.youtube.sponsorblock.objects.UserStats;
import app.revanced.extension.youtube.sponsorblock.requests.SBRequester;
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController;
import static android.text.Html.fromHtml;
import static app.revanced.extension.shared.StringRef.str;
@SuppressWarnings("deprecation")
public class SponsorBlockPreferenceFragment extends PreferenceFragment {
private SwitchPreference sbEnabled;
private SwitchPreference addNewSegment;
private SwitchPreference votingEnabled;
private SwitchPreference autoHideSkipSegmentButton;
private SwitchPreference compactSkipButton;
private SwitchPreference squareLayout;
private SwitchPreference showSkipToast;
private SwitchPreference trackSkips;
private SwitchPreference showTimeWithoutSegments;
private SwitchPreference toastOnConnectionError;
private EditTextPreference newSegmentStep;
private EditTextPreference minSegmentDuration;
private EditTextPreference privateUserId;
private EditTextPreference importExport;
private Preference apiUrl;
private PreferenceCategory statsCategory;
private PreferenceCategory segmentCategory;
private void updateUI() {
try {
final boolean enabled = Settings.SB_ENABLED.get();
if (!enabled) {
SponsorBlockViewController.hideAll();
SegmentPlaybackController.setCurrentVideoId(null);
} else if (!Settings.SB_CREATE_NEW_SEGMENT.get()) {
SponsorBlockViewController.hideNewSegmentLayout();
}
// Voting and add new segment buttons automatically show/hide themselves.
SponsorBlockViewController.updateLayout();
sbEnabled.setChecked(enabled);
addNewSegment.setChecked(Settings.SB_CREATE_NEW_SEGMENT.get());
addNewSegment.setEnabled(enabled);
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
votingEnabled.setEnabled(enabled);
autoHideSkipSegmentButton.setEnabled(enabled);
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
compactSkipButton.setEnabled(enabled);
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
squareLayout.setEnabled(enabled);
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
showSkipToast.setEnabled(enabled);
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
toastOnConnectionError.setEnabled(enabled);
trackSkips.setChecked(Settings.SB_TRACK_SKIP_COUNT.get());
trackSkips.setEnabled(enabled);
showTimeWithoutSegments.setChecked(Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get());
showTimeWithoutSegments.setEnabled(enabled);
newSegmentStep.setText((Settings.SB_CREATE_NEW_SEGMENT_STEP.get()).toString());
newSegmentStep.setEnabled(enabled);
minSegmentDuration.setText((Settings.SB_SEGMENT_MIN_DURATION.get()).toString());
minSegmentDuration.setEnabled(enabled);
privateUserId.setText(Settings.SB_PRIVATE_USER_ID.get());
privateUserId.setEnabled(enabled);
// If the user has a private user id, then include a subtext that mentions not to share it.
String importExportSummary = SponsorBlockSettings.userHasSBPrivateId()
? str("revanced_sb_settings_ie_sum_warning")
: str("revanced_sb_settings_ie_sum");
importExport.setSummary(importExportSummary);
apiUrl.setEnabled(enabled);
importExport.setEnabled(enabled);
segmentCategory.setEnabled(enabled);
statsCategory.setEnabled(enabled);
} catch (Exception ex) {
Logger.printException(() -> "update settings UI failure", ex);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
Activity context = getActivity();
PreferenceManager manager = getPreferenceManager();
manager.setSharedPreferencesName(Setting.preferences.name);
PreferenceScreen preferenceScreen = manager.createPreferenceScreen(context);
setPreferenceScreen(preferenceScreen);
SponsorBlockSettings.initialize();
sbEnabled = new SwitchPreference(context);
sbEnabled.setTitle(str("revanced_sb_enable_sb"));
sbEnabled.setSummary(str("revanced_sb_enable_sb_sum"));
preferenceScreen.addPreference(sbEnabled);
sbEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_ENABLED.save((Boolean) newValue);
updateUI();
return true;
});
addAppearanceCategory(context, preferenceScreen);
segmentCategory = new PreferenceCategory(context);
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
preferenceScreen.addPreference(segmentCategory);
updateSegmentCategories();
addCreateSegmentCategory(context, preferenceScreen);
addGeneralCategory(context, preferenceScreen);
statsCategory = new PreferenceCategory(context);
statsCategory.setTitle(str("revanced_sb_stats"));
preferenceScreen.addPreference(statsCategory);
fetchAndDisplayStats();
addAboutCategory(context, preferenceScreen);
updateUI();
} catch (Exception ex) {
Logger.printException(() -> "onCreate failure", ex);
}
}
private void addAppearanceCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("revanced_sb_appearance_category"));
votingEnabled = new SwitchPreference(context);
votingEnabled.setTitle(str("revanced_sb_enable_voting"));
votingEnabled.setSummaryOn(str("revanced_sb_enable_voting_sum_on"));
votingEnabled.setSummaryOff(str("revanced_sb_enable_voting_sum_off"));
category.addPreference(votingEnabled);
votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_VOTING_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
autoHideSkipSegmentButton = new SwitchPreference(context);
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
category.addPreference(autoHideSkipSegmentButton);
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
compactSkipButton = new SwitchPreference(context);
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
compactSkipButton.setSummaryOff(str("revanced_sb_enable_compact_skip_button_sum_off"));
category.addPreference(compactSkipButton);
compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_COMPACT_SKIP_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
squareLayout = new SwitchPreference(context);
squareLayout.setTitle(str("revanced_sb_square_layout"));
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
category.addPreference(squareLayout);
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
updateUI();
return true;
});
showSkipToast = new SwitchPreference(context);
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
showSkipToast.setSummaryOff(str("revanced_sb_general_skiptoast_sum_off"));
showSkipToast.setOnPreferenceClickListener(preference1 -> {
Utils.showToastShort(str("revanced_sb_skipped_sponsor"));
return false;
});
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
updateUI();
return true;
});
category.addPreference(showSkipToast);
showTimeWithoutSegments = new SwitchPreference(context);
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
showTimeWithoutSegments.setSummaryOff(str("revanced_sb_general_time_without_sum_off"));
showTimeWithoutSegments.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.save((Boolean) newValue);
updateUI();
return true;
});
category.addPreference(showTimeWithoutSegments);
}
private void addCreateSegmentCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("revanced_sb_create_segment_category"));
addNewSegment = new SwitchPreference(context);
addNewSegment.setTitle(str("revanced_sb_enable_create_segment"));
addNewSegment.setSummaryOn(str("revanced_sb_enable_create_segment_sum_on"));
addNewSegment.setSummaryOff(str("revanced_sb_enable_create_segment_sum_off"));
category.addPreference(addNewSegment);
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
Boolean newValue = (Boolean) o;
if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) {
new AlertDialog.Builder(preference1.getContext())
.setTitle(str("revanced_sb_guidelines_popup_title"))
.setMessage(str("revanced_sb_guidelines_popup_content"))
.setNegativeButton(str("revanced_sb_guidelines_popup_already_read"), null)
.setPositiveButton(str("revanced_sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines())
.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true))
.setCancelable(false)
.show();
}
Settings.SB_CREATE_NEW_SEGMENT.save(newValue);
updateUI();
return true;
});
newSegmentStep = new EditTextPreference(context);
newSegmentStep.setTitle(str("revanced_sb_general_adjusting"));
newSegmentStep.setSummary(str("revanced_sb_general_adjusting_sum"));
newSegmentStep.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
newSegmentStep.setOnPreferenceChangeListener((preference1, newValue) -> {
try {
final int newAdjustmentValue = Integer.parseInt(newValue.toString());
if (newAdjustmentValue != 0) {
Settings.SB_CREATE_NEW_SEGMENT_STEP.save(newAdjustmentValue);
return true;
}
} catch (NumberFormatException ex) {
Logger.printInfo(() -> "Invalid new segment step", ex);
}
Utils.showToastLong(str("revanced_sb_general_adjusting_invalid"));
updateUI();
return false;
});
category.addPreference(newSegmentStep);
Preference guidelinePreferences = new Preference(context);
guidelinePreferences.setTitle(str("revanced_sb_guidelines_preference_title"));
guidelinePreferences.setSummary(str("revanced_sb_guidelines_preference_sum"));
guidelinePreferences.setOnPreferenceClickListener(preference1 -> {
openGuidelines();
return true;
});
category.addPreference(guidelinePreferences);
}
private void addGeneralCategory(final Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("revanced_sb_general"));
toastOnConnectionError = new SwitchPreference(context);
toastOnConnectionError.setTitle(str("revanced_sb_toast_on_connection_error_title"));
toastOnConnectionError.setSummaryOn(str("revanced_sb_toast_on_connection_error_summary_on"));
toastOnConnectionError.setSummaryOff(str("revanced_sb_toast_on_connection_error_summary_off"));
toastOnConnectionError.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TOAST_ON_CONNECTION_ERROR.save((Boolean) newValue);
updateUI();
return true;
});
category.addPreference(toastOnConnectionError);
trackSkips = new SwitchPreference(context);
trackSkips.setTitle(str("revanced_sb_general_skipcount"));
trackSkips.setSummaryOn(str("revanced_sb_general_skipcount_sum_on"));
trackSkips.setSummaryOff(str("revanced_sb_general_skipcount_sum_off"));
trackSkips.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_TRACK_SKIP_COUNT.save((Boolean) newValue);
updateUI();
return true;
});
category.addPreference(trackSkips);
minSegmentDuration = new EditTextPreference(context);
minSegmentDuration.setTitle(str("revanced_sb_general_min_duration"));
minSegmentDuration.setSummary(str("revanced_sb_general_min_duration_sum"));
minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
minSegmentDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
try {
Float minTimeDuration = Float.valueOf(newValue.toString());
Settings.SB_SEGMENT_MIN_DURATION.save(minTimeDuration);
return true;
} catch (NumberFormatException ex) {
Logger.printInfo(() -> "Invalid minimum segment duration", ex);
}
Utils.showToastLong(str("revanced_sb_general_min_duration_invalid"));
updateUI();
return false;
});
category.addPreference(minSegmentDuration);
privateUserId = new EditTextPreference(context);
privateUserId.setTitle(str("revanced_sb_general_uuid"));
privateUserId.setSummary(str("revanced_sb_general_uuid_sum"));
privateUserId.setOnPreferenceChangeListener((preference1, newValue) -> {
String newUUID = newValue.toString();
if (!SponsorBlockSettings.isValidSBUserId(newUUID)) {
Utils.showToastLong(str("revanced_sb_general_uuid_invalid"));
return false;
}
Settings.SB_PRIVATE_USER_ID.save(newUUID);
updateUI();
fetchAndDisplayStats();
return true;
});
category.addPreference(privateUserId);
apiUrl = new Preference(context);
apiUrl.setTitle(str("revanced_sb_general_api_url"));
apiUrl.setSummary(Html.fromHtml(str("revanced_sb_general_api_url_sum")));
apiUrl.setOnPreferenceClickListener(preference1 -> {
EditText editText = new EditText(context);
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
editText.setText(Settings.SB_API_URL.get());
DialogInterface.OnClickListener urlChangeListener = (dialog, buttonPressed) -> {
if (buttonPressed == DialogInterface.BUTTON_NEUTRAL) {
Settings.SB_API_URL.resetToDefault();
Utils.showToastLong(str("revanced_sb_api_url_reset"));
} else if (buttonPressed == DialogInterface.BUTTON_POSITIVE) {
String serverAddress = editText.getText().toString();
if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) {
Utils.showToastLong(str("revanced_sb_api_url_invalid"));
} else if (!serverAddress.equals(Settings.SB_API_URL.get())) {
Settings.SB_API_URL.save(serverAddress);
Utils.showToastLong(str("revanced_sb_api_url_changed"));
}
}
};
new AlertDialog.Builder(context)
.setTitle(apiUrl.getTitle())
.setView(editText)
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(str("revanced_sb_reset"), urlChangeListener)
.setPositiveButton(android.R.string.ok, urlChangeListener)
.show();
return true;
});
category.addPreference(apiUrl);
importExport = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString());
});
}
};
importExport.setTitle(str("revanced_sb_settings_ie"));
// Summary is set in updateUI()
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
importExport.getEditText().setAutofillHints((String) null);
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
importExport.setOnPreferenceClickListener(preference1 -> {
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
return true;
});
importExport.setOnPreferenceChangeListener((preference1, newValue) -> {
SponsorBlockSettings.importDesktopSettings((String) newValue);
updateSegmentCategories();
fetchAndDisplayStats();
updateUI();
return true;
});
category.addPreference(importExport);
}
private void updateSegmentCategories() {
try {
segmentCategory.removeAll();
Activity activity = getActivity();
for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
segmentCategory.addPreference(new SegmentCategoryListPreference(activity, category));
}
} catch (Exception ex) {
Logger.printException(() -> "updateSegmentCategories failure", ex);
}
}
private void addAboutCategory(Context context, PreferenceScreen screen) {
PreferenceCategory category = new PreferenceCategory(context);
screen.addPreference(category);
category.setTitle(str("revanced_sb_about"));
{
Preference preference = new Preference(context);
category.addPreference(preference);
preference.setTitle(str("revanced_sb_about_api"));
preference.setSummary(str("revanced_sb_about_api_sum"));
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sponsor.ajay.app"));
preference1.getContext().startActivity(i);
return false;
});
}
}
private void openGuidelines() {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("https://wiki.sponsor.ajay.app/w/Guidelines"));
getActivity().startActivity(intent);
}
private void fetchAndDisplayStats() {
try {
statsCategory.removeAll();
if (!SponsorBlockSettings.userHasSBPrivateId()) {
// User has never voted or created any segments. No stats to show.
addLocalUserStats();
return;
}
Preference loadingPlaceholderPreference = new Preference(this.getActivity());
loadingPlaceholderPreference.setEnabled(false);
statsCategory.addPreference(loadingPlaceholderPreference);
if (Settings.SB_ENABLED.get()) {
loadingPlaceholderPreference.setTitle(str("revanced_sb_stats_loading"));
Utils.runOnBackgroundThread(() -> {
UserStats stats = SBRequester.retrieveUserStats();
Utils.runOnMainThread(() -> { // get back on main thread to modify UI elements
addUserStats(loadingPlaceholderPreference, stats);
addLocalUserStats();
});
});
} else {
loadingPlaceholderPreference.setTitle(str("revanced_sb_stats_sb_disabled"));
}
} catch (Exception ex) {
Logger.printException(() -> "fetchAndDisplayStats failure", ex);
}
}
private void addUserStats(@NonNull Preference loadingPlaceholder, @Nullable UserStats stats) {
Utils.verifyOnMainThread();
try {
if (stats == null) {
loadingPlaceholder.setTitle(str("revanced_sb_stats_connection_failure"));
return;
}
statsCategory.removeAll();
Context context = statsCategory.getContext();
if (stats.totalSegmentCountIncludingIgnored > 0) {
// If user has not created any segments, there's no reason to set a username.
EditTextPreference preference = new EditTextPreference(context);
statsCategory.addPreference(preference);
String userName = stats.userName;
preference.setTitle(fromHtml(str("revanced_sb_stats_username", userName)));
preference.setSummary(str("revanced_sb_stats_username_change"));
preference.setText(userName);
preference.setOnPreferenceChangeListener((preference1, value) -> {
Utils.runOnBackgroundThread(() -> {
String newUserName = (String) value;
String errorMessage = SBRequester.setUsername(newUserName);
Utils.runOnMainThread(() -> {
if (errorMessage == null) {
preference.setTitle(fromHtml(str("revanced_sb_stats_username", newUserName)));
preference.setText(newUserName);
Utils.showToastLong(str("revanced_sb_stats_username_changed"));
} else {
preference.setText(userName); // revert to previous
SponsorBlockUtils.showErrorDialog(errorMessage);
}
});
});
return true;
});
}
{
// number of segment submissions (does not include ignored segments)
Preference preference = new Preference(context);
statsCategory.addPreference(preference);
String formatted = SponsorBlockUtils.getNumberOfSkipsString(stats.segmentCount);
preference.setTitle(fromHtml(str("revanced_sb_stats_submissions", formatted)));
preference.setSummary(str("revanced_sb_stats_submissions_sum"));
if (stats.totalSegmentCountIncludingIgnored == 0) {
preference.setSelectable(false);
} else {
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sb.ltn.fi/userid/" + stats.publicUserId));
preference1.getContext().startActivity(i);
return true;
});
}
}
{
// "user reputation". Usually not useful, since it appears most users have zero reputation.
// But if there is a reputation, then show it here
Preference preference = new Preference(context);
preference.setTitle(fromHtml(str("revanced_sb_stats_reputation", stats.reputation)));
preference.setSelectable(false);
if (stats.reputation != 0) {
statsCategory.addPreference(preference);
}
}
{
// time saved for other users
Preference preference = new Preference(context);
statsCategory.addPreference(preference);
String stats_saved;
String stats_saved_sum;
if (stats.totalSegmentCountIncludingIgnored == 0) {
stats_saved = str("revanced_sb_stats_saved_zero");
stats_saved_sum = str("revanced_sb_stats_saved_sum_zero");
} else {
stats_saved = str("revanced_sb_stats_saved",
SponsorBlockUtils.getNumberOfSkipsString(stats.viewCount));
stats_saved_sum = str("revanced_sb_stats_saved_sum", SponsorBlockUtils.getTimeSavedString((long) (60 * stats.minutesSaved)));
}
preference.setTitle(fromHtml(stats_saved));
preference.setSummary(fromHtml(stats_saved_sum));
preference.setOnPreferenceClickListener(preference1 -> {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse("https://sponsor.ajay.app/stats/"));
preference1.getContext().startActivity(i);
return false;
});
}
} catch (Exception ex) {
Logger.printException(() -> "addUserStats failure", ex);
}
}
private void addLocalUserStats() {
// time the user saved by using SB
Preference preference = new Preference(statsCategory.getContext());
statsCategory.addPreference(preference);
Runnable updateStatsSelfSaved = () -> {
String formatted = SponsorBlockUtils.getNumberOfSkipsString(Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.get());
preference.setTitle(fromHtml(str("revanced_sb_stats_self_saved", formatted)));
String formattedSaved = SponsorBlockUtils.getTimeSavedString(Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.get() / 1000);
preference.setSummary(fromHtml(str("revanced_sb_stats_self_saved_sum", formattedSaved)));
};
updateStatsSelfSaved.run();
preference.setOnPreferenceClickListener(preference1 -> {
new AlertDialog.Builder(preference1.getContext())
.setTitle(str("revanced_sb_stats_self_saved_reset_title"))
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault();
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault();
updateStatsSelfSaved.run();
})
.setNegativeButton(android.R.string.no, null).show();
return true;
});
}
}

View File

@@ -73,6 +73,7 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
if (currentClientType == clientType) { if (currentClientType == clientType) {
return; return;
} }
currentClientType = clientType;
Logger.printDebug(() -> "Updating spoof stream side effects preference"); Logger.printDebug(() -> "Updating spoof stream side effects preference");
setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get()); setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get());

View File

@@ -0,0 +1,44 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.preference.Preference;
import android.util.AttributeSet;
import app.revanced.extension.shared.Logger;
/**
* Simple preference that opens a url when clicked.
*/
@SuppressWarnings("deprecation")
public class UrlLinkPreference extends Preference {
protected String externalUrl;
{
setOnPreferenceClickListener(pref -> {
if (externalUrl == null) {
Logger.printException(() -> "URL not set " + getClass().getSimpleName());
return false;
}
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(externalUrl));
pref.getContext().startActivity(i);
return true;
});
}
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public UrlLinkPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public UrlLinkPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public UrlLinkPreference(Context context) {
super(context);
}
}

View File

@@ -18,6 +18,7 @@ import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour; import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour;
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory; import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
@@ -31,6 +32,7 @@ public class SponsorBlockSettings {
@Override @Override
public void settingsImported(@Nullable Context context) { public void settingsImported(@Nullable Context context) {
SegmentCategory.loadAllCategoriesFromSettings(); SegmentCategory.loadAllCategoriesFromSettings();
SponsorBlockPreferenceGroup.settingsImported = true;
} }
@Override @Override
public void settingsExported(@Nullable Context context) { public void settingsExported(@Nullable Context context) {
@@ -136,7 +138,7 @@ public class SponsorBlockSettings {
for (SegmentCategory category : categories) { for (SegmentCategory category : categories) {
JSONObject categoryObject = new JSONObject(); JSONObject categoryObject = new JSONObject();
String categoryKey = category.keyValue; String categoryKey = category.keyValue;
categoryObject.put("color", category.colorString()); categoryObject.put("color", category.getColorString());
barTypesObject.put(categoryKey, categoryObject); barTypesObject.put(categoryKey, categoryObject);
if (category.behaviour != CategoryBehaviour.IGNORE) { if (category.behaviour != CategoryBehaviour.IGNORE) {

View File

@@ -5,7 +5,12 @@ import static app.revanced.extension.shared.StringRef.str;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.text.Html; import android.graphics.Color;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.widget.EditText; import android.widget.EditText;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -33,7 +38,7 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController
* Not thread safe. All fields/methods must be accessed from the main thread. * Not thread safe. All fields/methods must be accessed from the main thread.
*/ */
public class SponsorBlockUtils { public class SponsorBlockUtils {
private static final String LOCKED_COLOR = "#FFC83D"; private static final int LOCKED_COLOR = Color.parseColor("#FFC83D");
private static final String MANUAL_EDIT_TIME_TEXT_HINT = "hh:mm:ss.sss"; private static final String MANUAL_EDIT_TIME_TEXT_HINT = "hh:mm:ss.sss";
private static final Pattern manualEditTimePattern private static final Pattern manualEditTimePattern
= Pattern.compile("((\\d{1,2}):)?(\\d{1,2}):(\\d{2})(\\.(\\d{1,3}))?"); = Pattern.compile("((\\d{1,2}):)?(\\d{1,2}):(\\d{2})(\\.(\\d{1,3}))?");
@@ -160,32 +165,34 @@ public class SponsorBlockUtils {
SegmentVote[] voteOptions = (segment.category == SegmentCategory.HIGHLIGHT) SegmentVote[] voteOptions = (segment.category == SegmentCategory.HIGHLIGHT)
? SegmentVote.voteTypesWithoutCategoryChange // highlight segments cannot change category ? SegmentVote.voteTypesWithoutCategoryChange // highlight segments cannot change category
: SegmentVote.values(); : SegmentVote.values();
CharSequence[] items = new CharSequence[voteOptions.length]; final int voteOptionsLength = voteOptions.length;
final boolean userIsVip = Settings.SB_USER_IS_VIP.get();
CharSequence[] items = new CharSequence[voteOptionsLength];
for (int i = 0; i < voteOptions.length; i++) { for (int i = 0; i < voteOptionsLength; i++) {
SegmentVote voteOption = voteOptions[i]; SegmentVote voteOption = voteOptions[i];
String title = voteOption.title.toString(); CharSequence title = voteOption.title.toString();
if (Settings.SB_USER_IS_VIP.get() && segment.isLocked && voteOption.shouldHighlight) { if (userIsVip && segment.isLocked && voteOption.highlightIfVipAndVideoIsLocked) {
items[i] = Html.fromHtml(String.format("<font color=\"%s\">%s</font>", LOCKED_COLOR, title)); SpannableString coloredTitle = new SpannableString(title);
} else { coloredTitle.setSpan(new ForegroundColorSpan(LOCKED_COLOR),
items[i] = title; 0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
title = coloredTitle;
} }
items[i] = title;
} }
new AlertDialog.Builder(context) new AlertDialog.Builder(context).setItems(items, (dialog1, which1) -> {
.setItems(items, (dialog1, which1) -> { SegmentVote voteOption = voteOptions[which1];
SegmentVote voteOption = voteOptions[which1]; switch (voteOption) {
switch (voteOption) { case UPVOTE:
case UPVOTE: case DOWNVOTE:
case DOWNVOTE: SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption);
SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption); break;
break; case CATEGORY_CHANGE:
case CATEGORY_CHANGE: onNewCategorySelect(segment, context);
onNewCategorySelect(segment, context); break;
break; }
} }).show();
})
.show();
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "segmentVoteClickListener failure", ex); Logger.printException(() -> "segmentVoteClickListener failure", ex);
} }
@@ -282,7 +289,6 @@ public class SponsorBlockUtils {
return; return;
} }
final int numberOfSegments = segments.length; final int numberOfSegments = segments.length;
CharSequence[] titles = new CharSequence[numberOfSegments]; CharSequence[] titles = new CharSequence[numberOfSegments];
for (int i = 0; i < numberOfSegments; i++) { for (int i = 0; i < numberOfSegments; i++) {
@@ -290,22 +296,33 @@ public class SponsorBlockUtils {
if (segment.category == SegmentCategory.UNSUBMITTED) { if (segment.category == SegmentCategory.UNSUBMITTED) {
continue; continue;
} }
StringBuilder htmlBuilder = new StringBuilder();
htmlBuilder.append(String.format("<b><font color=\"#%06X\">⬤</font> %s<br>", SpannableStringBuilder spannableBuilder = new SpannableStringBuilder();
segment.category.color, segment.category.title));
htmlBuilder.append(formatSegmentTime(segment.start)); spannableBuilder.append(segment.category.getTitleWithColorDot());
if (segment.category != SegmentCategory.HIGHLIGHT) { spannableBuilder.append('\n');
htmlBuilder.append(" to ").append(formatSegmentTime(segment.end));
String startTime = formatSegmentTime(segment.start);
if (segment.category == SegmentCategory.HIGHLIGHT) {
spannableBuilder.append(startTime);
} else {
String toFromString = str("revanced_sb_vote_segment_time_to_from",
startTime, formatSegmentTime(segment.end));
spannableBuilder.append(toFromString);
} }
htmlBuilder.append("</b>");
if (i + 1 != numberOfSegments) // prevents trailing new line after last segment if (i + 1 != numberOfSegments) {
htmlBuilder.append("<br>"); // prevents trailing new line after last segment
titles[i] = Html.fromHtml(htmlBuilder.toString()); spannableBuilder.append('\n');
}
spannableBuilder.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
0, spannableBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
titles[i] = spannableBuilder;
} }
new AlertDialog.Builder(context) new AlertDialog.Builder(context).setItems(titles, segmentVoteClickListener).show();
.setItems(titles, segmentVoteClickListener)
.show();
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onVotingClicked failure", ex); Logger.printException(() -> "onVotingClicked failure", ex);
} }

View File

@@ -1,13 +1,14 @@
package app.revanced.extension.youtube.sponsorblock.objects; package app.revanced.extension.youtube.sponsorblock.objects;
import static app.revanced.extension.youtube.settings.Settings.*;
import static app.revanced.extension.shared.StringRef.sf; import static app.revanced.extension.shared.StringRef.sf;
import static app.revanced.extension.youtube.settings.Settings.*;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint; import android.graphics.Paint;
import android.text.Html; import android.text.Spannable;
import android.text.Spanned; import android.text.SpannableString;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -15,43 +16,45 @@ import androidx.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.StringRef; import app.revanced.extension.shared.StringRef;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.FloatSetting;
import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.youtube.settings.Settings;
public enum SegmentCategory { public enum SegmentCategory {
SPONSOR("sponsor", sf("revanced_sb_segments_sponsor"), sf("revanced_sb_segments_sponsor_sum"), sf("revanced_sb_skip_button_sponsor"), sf("revanced_sb_skipped_sponsor"), SPONSOR("sponsor", sf("revanced_sb_segments_sponsor"), sf("revanced_sb_segments_sponsor_sum"), sf("revanced_sb_skip_button_sponsor"), sf("revanced_sb_skipped_sponsor"),
SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR), SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR, SB_CATEGORY_SPONSOR_OPACITY),
SELF_PROMO("selfpromo", sf("revanced_sb_segments_selfpromo"), sf("revanced_sb_segments_selfpromo_sum"), sf("revanced_sb_skip_button_selfpromo"), sf("revanced_sb_skipped_selfpromo"), SELF_PROMO("selfpromo", sf("revanced_sb_segments_selfpromo"), sf("revanced_sb_segments_selfpromo_sum"), sf("revanced_sb_skip_button_selfpromo"), sf("revanced_sb_skipped_selfpromo"),
SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR), SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR, SB_CATEGORY_SELF_PROMO_OPACITY),
INTERACTION("interaction", sf("revanced_sb_segments_interaction"), sf("revanced_sb_segments_interaction_sum"), sf("revanced_sb_skip_button_interaction"), sf("revanced_sb_skipped_interaction"), INTERACTION("interaction", sf("revanced_sb_segments_interaction"), sf("revanced_sb_segments_interaction_sum"), sf("revanced_sb_skip_button_interaction"), sf("revanced_sb_skipped_interaction"),
SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR), SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR, SB_CATEGORY_INTERACTION_OPACITY),
/** /**
* Unique category that is treated differently than the rest. * Unique category that is treated differently than the rest.
*/ */
HIGHLIGHT("poi_highlight", sf("revanced_sb_segments_highlight"), sf("revanced_sb_segments_highlight_sum"), sf("revanced_sb_skip_button_highlight"), sf("revanced_sb_skipped_highlight"), HIGHLIGHT("poi_highlight", sf("revanced_sb_segments_highlight"), sf("revanced_sb_segments_highlight_sum"), sf("revanced_sb_skip_button_highlight"), sf("revanced_sb_skipped_highlight"),
SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR), SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR, SB_CATEGORY_HIGHLIGHT_OPACITY),
INTRO("intro", sf("revanced_sb_segments_intro"), sf("revanced_sb_segments_intro_sum"), INTRO("intro", sf("revanced_sb_segments_intro"), sf("revanced_sb_segments_intro_sum"),
sf("revanced_sb_skip_button_intro_beginning"), sf("revanced_sb_skip_button_intro_middle"), sf("revanced_sb_skip_button_intro_end"), sf("revanced_sb_skip_button_intro_beginning"), sf("revanced_sb_skip_button_intro_middle"), sf("revanced_sb_skip_button_intro_end"),
sf("revanced_sb_skipped_intro_beginning"), sf("revanced_sb_skipped_intro_middle"), sf("revanced_sb_skipped_intro_end"), sf("revanced_sb_skipped_intro_beginning"), sf("revanced_sb_skipped_intro_middle"), sf("revanced_sb_skipped_intro_end"),
SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR), SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR, SB_CATEGORY_INTRO_OPACITY),
OUTRO("outro", sf("revanced_sb_segments_outro"), sf("revanced_sb_segments_outro_sum"), sf("revanced_sb_skip_button_outro"), sf("revanced_sb_skipped_outro"), OUTRO("outro", sf("revanced_sb_segments_outro"), sf("revanced_sb_segments_outro_sum"), sf("revanced_sb_skip_button_outro"), sf("revanced_sb_skipped_outro"),
SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR), SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR, SB_CATEGORY_OUTRO_OPACITY),
PREVIEW("preview", sf("revanced_sb_segments_preview"), sf("revanced_sb_segments_preview_sum"), PREVIEW("preview", sf("revanced_sb_segments_preview"), sf("revanced_sb_segments_preview_sum"),
sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"), sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"),
sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"), sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"),
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR), SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY),
FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"), FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"),
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR), SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY),
MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"), MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"),
SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR), SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR, SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY),
UNSUBMITTED("unsubmitted", StringRef.empty, StringRef.empty, sf("revanced_sb_skip_button_unsubmitted"), sf("revanced_sb_skipped_unsubmitted"), UNSUBMITTED("unsubmitted", StringRef.empty, StringRef.empty, sf("revanced_sb_skip_button_unsubmitted"), sf("revanced_sb_skipped_unsubmitted"),
SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR),; SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR, SB_CATEGORY_UNSUBMITTED_OPACITY);
private static final StringRef skipSponsorTextCompact = sf("revanced_sb_skip_button_compact"); private static final StringRef skipSponsorTextCompact = sf("revanced_sb_skip_button_compact");
private static final StringRef skipSponsorTextCompactHighlight = sf("revanced_sb_skip_button_compact_highlight"); private static final StringRef skipSponsorTextCompactHighlight = sf("revanced_sb_skip_button_compact_highlight");
@@ -90,12 +93,10 @@ public enum SegmentCategory {
mValuesMap.put(value.keyValue, value); mValuesMap.put(value.keyValue, value);
} }
@NonNull
public static SegmentCategory[] categoriesWithoutUnsubmitted() { public static SegmentCategory[] categoriesWithoutUnsubmitted() {
return categoriesWithoutUnsubmitted; return categoriesWithoutUnsubmitted;
} }
@NonNull
public static SegmentCategory[] categoriesWithoutHighlights() { public static SegmentCategory[] categoriesWithoutHighlights() {
return categoriesWithoutHighlights; return categoriesWithoutHighlights;
} }
@@ -106,7 +107,7 @@ public enum SegmentCategory {
} }
/** /**
* Must be called if behavior of any category is changed * Must be called if behavior of any category is changed.
*/ */
public static void updateEnabledCategories() { public static void updateEnabledCategories() {
Utils.verifyOnMainThread(); Utils.verifyOnMainThread();
@@ -133,32 +134,33 @@ public enum SegmentCategory {
updateEnabledCategories(); updateEnabledCategories();
} }
@NonNull public static int applyOpacityToColor(int color, float opacity) {
public final String keyValue; if (opacity < 0 || opacity > 1.0f) {
@NonNull throw new IllegalArgumentException("Invalid opacity: " + opacity);
public final StringSetting behaviorSetting; }
@NonNull final int opacityInt = (int) (255 * opacity);
private final StringSetting colorSetting; return (color & 0x00FFFFFF) | (opacityInt << 24);
}
public final String keyValue;
public final StringSetting behaviorSetting; // TODO: Replace with EnumSetting.
private final StringSetting colorSetting;
private final FloatSetting opacitySetting;
@NonNull
public final StringRef title; public final StringRef title;
@NonNull
public final StringRef description; public final StringRef description;
/** /**
* Skip button text, if the skip occurs in the first quarter of the video * Skip button text, if the skip occurs in the first quarter of the video
*/ */
@NonNull
public final StringRef skipButtonTextBeginning; public final StringRef skipButtonTextBeginning;
/** /**
* Skip button text, if the skip occurs in the middle half of the video * Skip button text, if the skip occurs in the middle half of the video
*/ */
@NonNull
public final StringRef skipButtonTextMiddle; public final StringRef skipButtonTextMiddle;
/** /**
* Skip button text, if the skip occurs in the last quarter of the video * Skip button text, if the skip occurs in the last quarter of the video
*/ */
@NonNull
public final StringRef skipButtonTextEnd; public final StringRef skipButtonTextEnd;
/** /**
* Skipped segment toast, if the skip occurred in the first quarter of the video * Skipped segment toast, if the skip occurred in the first quarter of the video
@@ -179,10 +181,7 @@ public enum SegmentCategory {
@NonNull @NonNull
public final Paint paint; public final Paint paint;
/** private int color;
* Value must be changed using {@link #setColor(String)}.
*/
public int color;
/** /**
* Value must be changed using {@link #setBehaviour(CategoryBehaviour)}. * Value must be changed using {@link #setBehaviour(CategoryBehaviour)}.
@@ -194,17 +193,20 @@ public enum SegmentCategory {
SegmentCategory(String keyValue, StringRef title, StringRef description, SegmentCategory(String keyValue, StringRef title, StringRef description,
StringRef skipButtonText, StringRef skipButtonText,
StringRef skippedToastText, StringRef skippedToastText,
StringSetting behavior, StringSetting color) { StringSetting behavior,
StringSetting color, FloatSetting opacity) {
this(keyValue, title, description, this(keyValue, title, description,
skipButtonText, skipButtonText, skipButtonText, skipButtonText, skipButtonText, skipButtonText,
skippedToastText, skippedToastText, skippedToastText, skippedToastText, skippedToastText, skippedToastText,
behavior, color); behavior,
color, opacity);
} }
SegmentCategory(String keyValue, StringRef title, StringRef description, SegmentCategory(String keyValue, StringRef title, StringRef description,
StringRef skipButtonTextBeginning, StringRef skipButtonTextMiddle, StringRef skipButtonTextEnd, StringRef skipButtonTextBeginning, StringRef skipButtonTextMiddle, StringRef skipButtonTextEnd,
StringRef skippedToastBeginning, StringRef skippedToastMiddle, StringRef skippedToastEnd, StringRef skippedToastBeginning, StringRef skippedToastMiddle, StringRef skippedToastEnd,
StringSetting behavior, StringSetting color) { StringSetting behavior,
StringSetting color, FloatSetting opacity) {
this.keyValue = Objects.requireNonNull(keyValue); this.keyValue = Objects.requireNonNull(keyValue);
this.title = Objects.requireNonNull(title); this.title = Objects.requireNonNull(title);
this.description = Objects.requireNonNull(description); this.description = Objects.requireNonNull(description);
@@ -216,6 +218,7 @@ public enum SegmentCategory {
this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd); this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd);
this.behaviorSetting = Objects.requireNonNull(behavior); this.behaviorSetting = Objects.requireNonNull(behavior);
this.colorSetting = Objects.requireNonNull(color); this.colorSetting = Objects.requireNonNull(color);
this.opacitySetting = Objects.requireNonNull(opacity);
this.paint = new Paint(); this.paint = new Paint();
loadFromSettings(); loadFromSettings();
} }
@@ -232,11 +235,14 @@ public enum SegmentCategory {
this.behaviour = savedBehavior; this.behaviour = savedBehavior;
String colorString = colorSetting.get(); String colorString = colorSetting.get();
final float opacity = opacitySetting.get();
try { try {
setColor(colorString); setColor(colorString);
setOpacity(opacity);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "Invalid color: " + colorString, ex); Logger.printException(() -> "Invalid color: " + colorString + " opacity: " + opacity, ex);
colorSetting.resetToDefault(); colorSetting.resetToDefault();
opacitySetting.resetToDefault();
loadFromSettings(); loadFromSettings();
} }
} }
@@ -245,45 +251,78 @@ public enum SegmentCategory {
this.behaviour = Objects.requireNonNull(behaviour); this.behaviour = Objects.requireNonNull(behaviour);
this.behaviorSetting.save(behaviour.reVancedKeyValue); this.behaviorSetting.save(behaviour.reVancedKeyValue);
} }
/**
* @return HTML color format string
*/
@NonNull
public String colorString() {
return String.format("#%06X", color);
}
public void setColor(@NonNull String colorString) throws IllegalArgumentException { private void updateColor() {
final int color = Color.parseColor(colorString) & 0xFFFFFF; color = applyOpacityToColor(color, opacitySetting.get());
this.color = color;
paint.setColor(color); paint.setColor(color);
paint.setAlpha(255);
colorSetting.save(colorString); // Save after parsing.
} }
public void resetColor() { /**
* @param opacity Segment color opacity between [0, 1].
*/
public void setOpacity(float opacity) throws IllegalArgumentException {
if (opacity < 0 || opacity > 1) {
throw new IllegalArgumentException("Invalid opacity: " + opacity);
}
opacitySetting.save(opacity);
updateColor();
}
public float getOpacity() {
return opacitySetting.get();
}
public void resetColorAndOpacity() {
setColor(colorSetting.defaultValue); setColor(colorSetting.defaultValue);
setOpacity(opacitySetting.defaultValue);
} }
@NonNull /**
private static String getCategoryColorDotHTML(int color) { * @param colorString Segment color with #RRGGBB format.
color &= 0xFFFFFF; */
return String.format("<font color=\"#%06X\">⬤</font>", color); public void setColor(String colorString) throws IllegalArgumentException {
color = Color.parseColor(colorString);
colorSetting.save(colorString);
updateColor();
} }
@NonNull /**
public static Spanned getCategoryColorDot(int color) { * @return Integer color of #RRGGBB format.
return Html.fromHtml(getCategoryColorDotHTML(color)); */
public int getColorNoOpacity() {
return color & 0x00FFFFFF;
} }
@NonNull /**
public Spanned getCategoryColorDot() { * @return Hex color string of #RRGGBB format with no opacity level.
*/
public String getColorString() {
return String.format(Locale.US, "#%06X", getColorNoOpacity());
}
private static SpannableString getCategoryColorDotSpan(String text, int color) {
SpannableString dotSpan = new SpannableString('⬤' + text);
dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return dotSpan;
}
public static SpannableString getCategoryColorDot(int color) {
return getCategoryColorDotSpan("", color);
}
public SpannableString getCategoryColorDot() {
return getCategoryColorDot(color); return getCategoryColorDot(color);
} }
@NonNull public SpannableString getTitleWithColorDot(int categoryColor) {
public Spanned getTitleWithColorDot() { return getCategoryColorDotSpan(" " + title, categoryColor);
return Html.fromHtml(getCategoryColorDotHTML(color) + " " + title); }
public SpannableString getTitleWithColorDot() {
return getTitleWithColorDot(color);
} }
/** /**
@@ -291,7 +330,6 @@ public enum SegmentCategory {
* @param videoLength length of the video * @param videoLength length of the video
* @return the skip button text * @return the skip button text
*/ */
@NonNull
StringRef getSkipButtonText(long segmentStartTime, long videoLength) { StringRef getSkipButtonText(long segmentStartTime, long videoLength) {
if (Settings.SB_COMPACT_SKIP_BUTTON.get()) { if (Settings.SB_COMPACT_SKIP_BUTTON.get()) {
return (this == SegmentCategory.HIGHLIGHT) return (this == SegmentCategory.HIGHLIGHT)
@@ -300,7 +338,7 @@ public enum SegmentCategory {
} }
if (videoLength == 0) { if (videoLength == 0) {
return skipButtonTextBeginning; // video is still loading. Assume it's the beginning return skipButtonTextBeginning; // Video is still loading. Assume it's the beginning.
} }
final float position = segmentStartTime / (float) videoLength; final float position = segmentStartTime / (float) videoLength;
if (position < 0.25f) { if (position < 0.25f) {
@@ -316,10 +354,9 @@ public enum SegmentCategory {
* @param videoLength length of the video * @param videoLength length of the video
* @return 'skipped segment' toast message * @return 'skipped segment' toast message
*/ */
@NonNull
StringRef getSkippedToastText(long segmentStartTime, long videoLength) { StringRef getSkippedToastText(long segmentStartTime, long videoLength) {
if (videoLength == 0) { if (videoLength == 0) {
return skippedToastBeginning; // video is still loading. Assume it's the beginning return skippedToastBeginning; // Video is still loading. Assume it's the beginning.
} }
final float position = segmentStartTime / (float) videoLength; final float position = segmentStartTime / (float) videoLength;
if (position < 0.25f) { if (position < 0.25f) {

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