mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-07 01:51:27 +01:00
Compare commits
234 Commits
v5.14.0-de
...
v5.24.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a3a7f1674 | ||
|
|
e59c9e9b3c | ||
|
|
dfb552b01a | ||
|
|
94999c56b1 | ||
|
|
c4fd1f0146 | ||
|
|
4cd0ae9b92 | ||
|
|
9548d581c1 | ||
|
|
a2fe3af6be | ||
|
|
6ef6504d41 | ||
|
|
e58290839f | ||
|
|
e18260bd65 | ||
|
|
b2fcd5a846 | ||
|
|
e68cd70f66 | ||
|
|
14a8f4fb96 | ||
|
|
2593c004f4 | ||
|
|
db68c41d5e | ||
|
|
a4f9cb3cef | ||
|
|
9aec1999bb | ||
|
|
26ecbe646e | ||
|
|
46ba0d8a2e | ||
|
|
f454183646 | ||
|
|
d2b440d800 | ||
|
|
494c5f04a4 | ||
|
|
48d5fdf7e1 | ||
|
|
887c9f0d75 | ||
|
|
7de4c9d41d | ||
|
|
7d3b8d9c42 | ||
|
|
25e1a965d6 | ||
|
|
b29c01cee1 | ||
|
|
639850471b | ||
|
|
796c118fe1 | ||
|
|
edf20e397d | ||
|
|
5f0541407c | ||
|
|
56b7ba9ba7 | ||
|
|
f8bdf744ab | ||
|
|
f4f36ff273 | ||
|
|
5028c1acb3 | ||
|
|
555c9a5823 | ||
|
|
777957e2d0 | ||
|
|
b3316a5915 | ||
|
|
2ca2bb7692 | ||
|
|
23fd720fa7 | ||
|
|
1f08586ae8 | ||
|
|
60fdf4c44c | ||
|
|
63f3342815 | ||
|
|
858c59d728 | ||
|
|
5debf9936d | ||
|
|
f1b85d20a1 | ||
|
|
37d0de5e93 | ||
|
|
96d08d5eb7 | ||
|
|
9b1013e1c2 | ||
|
|
75d6cd7c7b | ||
|
|
5a17f5e1c1 | ||
|
|
1d16de6617 | ||
|
|
aee7cba46d | ||
|
|
ec3faf30a8 | ||
|
|
45b5a51da3 | ||
|
|
8abf176bc9 | ||
|
|
ef35ed7335 | ||
|
|
4fd666b667 | ||
|
|
72e0c01922 | ||
|
|
f69eab3e3b | ||
|
|
7c5c2d95bc | ||
|
|
b2453fecfc | ||
|
|
0d54f8bd80 | ||
|
|
fda16fad1a | ||
|
|
ddd43acd73 | ||
|
|
3451318d53 | ||
|
|
2d94ba9df6 | ||
|
|
aaf3437a5a | ||
|
|
ec8bf06047 | ||
|
|
96512de6c9 | ||
|
|
6114807c43 | ||
|
|
6d69f01421 | ||
|
|
fd4218154d | ||
|
|
8bed8a6622 | ||
|
|
3174047223 | ||
|
|
15053e2b68 | ||
|
|
e5b6aac018 | ||
|
|
d7c9dd0f77 | ||
|
|
a0eb6d5fdb | ||
|
|
55c5eb3d14 | ||
|
|
896de8910a | ||
|
|
e2a7e25c66 | ||
|
|
77ea5c4033 | ||
|
|
6eea2354f5 | ||
|
|
cce21c4d4a | ||
|
|
5e069bde90 | ||
|
|
6a49208982 | ||
|
|
404bb2e86e | ||
|
|
bc869fe359 | ||
|
|
7d166cf82c | ||
|
|
8efbaae65c | ||
|
|
e27ab23279 | ||
|
|
ce42604083 | ||
|
|
fc6282d0cb | ||
|
|
0559fc7fd0 | ||
|
|
7cc6995682 | ||
|
|
476f13bf98 | ||
|
|
f216e16c0b | ||
|
|
f2a8789649 | ||
|
|
5973b64f52 | ||
|
|
102036706e | ||
|
|
2393d0a8f5 | ||
|
|
aea29b9522 | ||
|
|
4db8ef7079 | ||
|
|
7fbd26ccad | ||
|
|
91995ea01d | ||
|
|
86f867fe97 | ||
|
|
0f687ecfd3 | ||
|
|
6c8b7d09c1 | ||
|
|
3d6958f157 | ||
|
|
43d7cc7374 | ||
|
|
5ebd449f1f | ||
|
|
346a061df8 | ||
|
|
13e490a422 | ||
|
|
b4e8540bbc | ||
|
|
775c1baec2 | ||
|
|
9419fb8ec4 | ||
|
|
c510931eb0 | ||
|
|
7160699384 | ||
|
|
9db67a6eb2 | ||
|
|
e684d87dd3 | ||
|
|
2d1752a1eb | ||
|
|
c9ff7092fe | ||
|
|
d451bc6d6d | ||
|
|
741fd36872 | ||
|
|
517f8cf59a | ||
|
|
b78fb24435 | ||
|
|
a3faccb21b | ||
|
|
5f0fddc122 | ||
|
|
854a18ff72 | ||
|
|
b994a16bdc | ||
|
|
f68d06dbf3 | ||
|
|
04c6a2e5f4 | ||
|
|
e6ae55fa99 | ||
|
|
fb62474ff4 | ||
|
|
e084f01fd0 | ||
|
|
d573386e0f | ||
|
|
0f3aeb35e5 | ||
|
|
e30f593af0 | ||
|
|
df965b8a9b | ||
|
|
654587a75e | ||
|
|
9956833781 | ||
|
|
c585b26188 | ||
|
|
de0d11fcfb | ||
|
|
d321504fcf | ||
|
|
6005c97bf5 | ||
|
|
e404d84c83 | ||
|
|
1abed31968 | ||
|
|
a75a88d3c6 | ||
|
|
3d67d90473 | ||
|
|
fa1e137a43 | ||
|
|
ac71a53c73 | ||
|
|
0bff207efc | ||
|
|
e1a8b388a5 | ||
|
|
628d18489c | ||
|
|
36772b8b2e | ||
|
|
49c849979f | ||
|
|
0bdb8cdf2b | ||
|
|
2035c9e2e9 | ||
|
|
7cb38fd3fc | ||
|
|
8ed9d5bf08 | ||
|
|
cd467d6244 | ||
|
|
fdefb67d02 | ||
|
|
5274cd18f0 | ||
|
|
3d68c06146 | ||
|
|
ef3d5bafd5 | ||
|
|
2d7b1b09af | ||
|
|
0572d48fde | ||
|
|
37984b8b99 | ||
|
|
6e63193f06 | ||
|
|
b2384b22a5 | ||
|
|
ccb76983ff | ||
|
|
318b55b8fe | ||
|
|
49ade9efbc | ||
|
|
d77515bd68 | ||
|
|
087bf1e152 | ||
|
|
c2994d583d | ||
|
|
127b0a63fe | ||
|
|
27aafd0ee1 | ||
|
|
49c54c0e54 | ||
|
|
842ba4fc4d | ||
|
|
66ecadce4f | ||
|
|
73ca04da5e | ||
|
|
a5d26208c1 | ||
|
|
497291c478 | ||
|
|
b24278a544 | ||
|
|
135f9ead3c | ||
|
|
ca4f960171 | ||
|
|
7f228cc535 | ||
|
|
bf91e127d8 | ||
|
|
f07fc1ad93 | ||
|
|
c84be120bd | ||
|
|
e67f390e2b | ||
|
|
4d910fea93 | ||
|
|
72adbe5519 | ||
|
|
54d49b774e | ||
|
|
283bb31567 | ||
|
|
2724fcbd27 | ||
|
|
7c28193579 | ||
|
|
cd1ee814c4 | ||
|
|
d9ccd73b5f | ||
|
|
5c5a1e4b8b | ||
|
|
66a2ee2416 | ||
|
|
d8c276cf96 | ||
|
|
d5845abd08 | ||
|
|
54eef22ce7 | ||
|
|
e287bdc59d | ||
|
|
20a82ef956 | ||
|
|
1e29da9e06 | ||
|
|
56e6a90a90 | ||
|
|
76d32e21c2 | ||
|
|
54a7afa540 | ||
|
|
ef86438bac | ||
|
|
0683cedac0 | ||
|
|
35753410aa | ||
|
|
df838ed91d | ||
|
|
8e494d26d4 | ||
|
|
7d834e5421 | ||
|
|
60a31cf4e1 | ||
|
|
edb8bd66bc | ||
|
|
04a170054e | ||
|
|
79e6349a69 | ||
|
|
bbf3a34a2f | ||
|
|
1db7c49514 | ||
|
|
ef0506a4f8 | ||
|
|
9b38da35ff | ||
|
|
afdb771066 | ||
|
|
1b2b536d2e | ||
|
|
f39e70c648 | ||
|
|
556acdd9c1 | ||
|
|
7adfc637dc | ||
|
|
9cc0c075ad |
2
.github/workflows/pull_strings.yml
vendored
2
.github/workflows/pull_strings.yml
vendored
@@ -2,7 +2,7 @@ name: Pull strings
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */8 * * *"
|
||||
- cron: "0 */12 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
772
CHANGELOG.md
772
CHANGELOG.md
@@ -1,3 +1,775 @@
|
||||
# [5.24.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.7...v5.24.0-dev.8) (2025-05-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Settings:** Correctly show summary text if search box is closed before searching ([d0ae835](https://github.com/ReVanced/revanced-patches/commit/d0ae835d3381fc659c9bb4a2d130d4db8a1499cf))
|
||||
|
||||
# [5.24.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.6...v5.24.0-dev.7) (2025-05-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide layout components:** Add `Hide ticket shelf` ([#4969](https://github.com/ReVanced/revanced-patches/issues/4969)) ([6436af7](https://github.com/ReVanced/revanced-patches/commit/6436af7e77c77d2034dfceba8bc51132ad7632be))
|
||||
|
||||
# [5.24.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.24.0-dev.5...v5.24.0-dev.6) (2025-05-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide description components:** Add `Hide Ask` ([#4972](https://github.com/ReVanced/revanced-patches/issues/4972)) ([ebc94a5](https://github.com/ReVanced/revanced-patches/commit/ebc94a5da6214b67399c9c01515689bd4b20547c))
|
||||
|
||||
# [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)
|
||||
|
||||
|
||||
|
||||
16
extensions/all/misc/adb/hide-adb/build.gradle.kts
Normal file
16
extensions/all/misc/adb/hide-adb/build.gradle.kts
Normal 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)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.content.Context;
|
||||
@@ -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.os.Build;
|
||||
@@ -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.WindowManager;
|
||||
@@ -82,7 +82,7 @@ public class HideAdsPatch {
|
||||
|
||||
// Filter HeaderBlock with known ads until next HeaderBlock.
|
||||
if (currentBlock instanceof HeaderBlock headerBlock) {
|
||||
StyledText headerText = headerBlock.component20();
|
||||
StyledText headerText = headerBlock.getTitle();
|
||||
if (headerText != null) {
|
||||
skipFullHeader = false;
|
||||
for (String blockedHeaderBlock : blockedHeaderBlocks) {
|
||||
|
||||
@@ -3,8 +3,7 @@ package nl.nu.performance.api.client.objects;
|
||||
import nl.nu.performance.api.client.interfaces.Block;
|
||||
|
||||
public class HeaderBlock extends Block {
|
||||
// returns title
|
||||
public final StyledText component20() {
|
||||
public final StyledText getTitle() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
|
||||
4
extensions/primevideo/build.gradle.kts
Normal file
4
extensions/primevideo/build.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:primevideo:stub"))
|
||||
}
|
||||
1
extensions/primevideo/src/main/AndroidManifest.xml
Normal file
1
extensions/primevideo/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/primevideo/stub/build.gradle.kts
Normal file
17
extensions/primevideo/stub/build.gradle.kts
Normal 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
|
||||
}
|
||||
}
|
||||
1
extensions/primevideo/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/primevideo/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.amazon.avod.fsm;
|
||||
|
||||
public final class SimpleTrigger<T> implements Trigger<T> {
|
||||
public SimpleTrigger(T triggerType) {
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.fsm;
|
||||
|
||||
public interface Trigger<T> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.amazon.avod.media;
|
||||
|
||||
public final class TimeSpan {
|
||||
public long getTotalMilliseconds() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.amazon.avod.media.ads;
|
||||
|
||||
import com.amazon.avod.media.TimeSpan;
|
||||
|
||||
public interface AdBreak {
|
||||
TimeSpan getDurationExcludingAux();
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public abstract class AdBreakState extends AdEnabledPlaybackState {
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public enum AdEnabledPlayerTriggerType {
|
||||
NO_MORE_ADS_SKIP_TRANSITION
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.media.ads.internal.state;
|
||||
|
||||
public class ServerInsertedAdBreakState extends AdBreakState {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.amazon.avod.media.playback;
|
||||
|
||||
public interface VideoPlayer {
|
||||
long getCurrentPosition();
|
||||
|
||||
void seekTo(long positionMs);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.media.playback.state;
|
||||
|
||||
public interface PlayerStateType {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.amazon.avod.media.playback.state.trigger;
|
||||
|
||||
public interface PlayerTriggerType {
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.extension.shared;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
@@ -15,14 +16,18 @@ import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
import app.revanced.extension.shared.requests.Requester;
|
||||
import app.revanced.extension.shared.requests.Route;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class GmsCoreSupport {
|
||||
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";
|
||||
@@ -31,10 +36,24 @@ public class GmsCoreSupport {
|
||||
= getGmsCoreVendorGroupId() + ".android.gms";
|
||||
private static final Uri GMS_CORE_PROVIDER
|
||||
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
|
||||
private static final String DONT_KILL_MY_APP_LINK
|
||||
= "https://dontkillmyapp.com";
|
||||
private static final String DONT_KILL_MY_APP_URL
|
||||
= "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) {
|
||||
Logger.printInfo(() -> "Opening link: " + queryOrLink);
|
||||
|
||||
Intent intent;
|
||||
try {
|
||||
// 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
|
||||
// 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.
|
||||
}
|
||||
|
||||
@@ -124,11 +143,12 @@ public class GmsCoreSupport {
|
||||
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
||||
if (client == null) {
|
||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||
checkIfDontKillMyAppSupportsManufacturer();
|
||||
|
||||
showBatteryOptimizationDialog(context,
|
||||
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
||||
"gms_core_dialog_open_website_text",
|
||||
(dialog, id) -> open(DONT_KILL_MY_APP_LINK));
|
||||
(dialog, id) -> openDontKillMyApp());
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
@@ -143,6 +163,48 @@ public class GmsCoreSupport {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
@@ -370,7 +371,7 @@ public class Utils {
|
||||
if (language != AppLanguage.DEFAULT) {
|
||||
// Create a new context with the desired language.
|
||||
Logger.printDebug(() -> "Using app language: " + language);
|
||||
Configuration config = appContext.getResources().getConfiguration();
|
||||
Configuration config = new Configuration(appContext.getResources().getConfiguration());
|
||||
config.setLocale(language.getLocale());
|
||||
context = appContext.createConfigurationContext(config);
|
||||
}
|
||||
@@ -390,16 +391,47 @@ public class Utils {
|
||||
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) {
|
||||
String displayLanguage = Locale.getDefault().getDisplayLanguage();
|
||||
isRightToLeftTextLayout = new Bidi(displayLanguage, Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT).isRightToLeft();
|
||||
isRightToLeftTextLayout = isRightToLeftLocale(Locale.getDefault());
|
||||
}
|
||||
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,
|
||||
* 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.
|
||||
*/
|
||||
public static String removePunctuationConvertToLowercase(@Nullable CharSequence original) {
|
||||
public static String removePunctuationToLowercase(@Nullable CharSequence original) {
|
||||
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;
|
||||
switch (preferenceSort) {
|
||||
case BY_TITLE:
|
||||
sortValue = removePunctuationConvertToLowercase(preference.getTitle());
|
||||
sortValue = removePunctuationToLowercase(preference.getTitle());
|
||||
break;
|
||||
case BY_KEY:
|
||||
sortValue = preference.getKey();
|
||||
@@ -799,4 +832,22 @@ public class Utils {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,9 +89,11 @@ public enum AppLanguage {
|
||||
ZU;
|
||||
|
||||
private final String language;
|
||||
private final Locale locale;
|
||||
|
||||
AppLanguage() {
|
||||
language = name().toLowerCase(Locale.US);
|
||||
locale = Locale.forLanguageTag(language);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,6 +114,6 @@ public enum AppLanguage {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
|
||||
return Locale.forLanguageTag(language);
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
/**
|
||||
* 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 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));
|
||||
|
||||
@@ -342,9 +342,12 @@ public abstract class Setting<T> {
|
||||
|
||||
/**
|
||||
* Identical to calling {@link #save(Object)} using {@link #defaultValue}.
|
||||
*
|
||||
* @return The newly saved default value.
|
||||
*/
|
||||
public void resetToDefault() {
|
||||
public T resetToDefault() {
|
||||
save(defaultValue);
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -22,12 +22,23 @@ import app.revanced.extension.shared.settings.Setting;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
/**
|
||||
* Indicates that if a preference changes,
|
||||
* to apply the change from the Setting to the UI component.
|
||||
*/
|
||||
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.
|
||||
* Set by subclasses if Strings cannot be added as a resource.
|
||||
@@ -35,13 +46,13 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
@Nullable
|
||||
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) -> {
|
||||
try {
|
||||
if (updatingPreference) {
|
||||
Logger.printDebug(() -> "Ignoring preference change as sync is in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
|
||||
if (setting == null) {
|
||||
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'.
|
||||
// Updating here can can cause a recursive call back into this same method.
|
||||
updatePreference(pref, setting, true, settingImportInProgress);
|
||||
// Update any other preference availability that may now be different.
|
||||
updateUIAvailability();
|
||||
updatingPreference = false;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Initialize this instance, and do any custom behavior.
|
||||
* <p>
|
||||
@@ -81,7 +94,10 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
* so all app specific {@link Setting} instances are loaded before this method returns.
|
||||
*/
|
||||
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;
|
||||
addPreferencesFromResource(identifier);
|
||||
|
||||
@@ -97,7 +113,9 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
if (confirmDialogTitle == null) {
|
||||
confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title");
|
||||
}
|
||||
|
||||
showingUserDialogMessage = true;
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(confirmDialogTitle)
|
||||
.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.
|
||||
*/
|
||||
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
|
||||
Object defaultValue = setting.defaultValue;
|
||||
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) {
|
||||
return editPreference.getText().equals(setting.defaultValue.toString());
|
||||
return editPreference.getText().equals(defaultValueString);
|
||||
}
|
||||
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 "
|
||||
@@ -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();
|
||||
if (restartDialogTitle == null) {
|
||||
restartDialogTitle = str("revanced_settings_restart_title");
|
||||
@@ -263,6 +283,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
if (restartDialogButtonText == null) {
|
||||
restartDialogButtonText = str("revanced_settings_restart");
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(restartDialogTitle)
|
||||
.setPositiveButton(restartDialogButtonText, (dialog, id)
|
||||
|
||||
@@ -21,6 +21,10 @@ public class NoTitlePreferenceCategory extends PreferenceCategory {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public NoTitlePreferenceCategory(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@@ -8,17 +10,23 @@ import android.util.AttributeSet;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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"})
|
||||
public class ResettableEditTextPreference extends EditTextPreference {
|
||||
|
||||
/**
|
||||
* Setting to reset.
|
||||
*/
|
||||
@Nullable
|
||||
private Setting<?> setting;
|
||||
|
||||
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
@@ -32,12 +40,22 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setSetting(@Nullable Setting<?> setting) {
|
||||
this.setting = setting;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(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) {
|
||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
||||
}
|
||||
@@ -54,8 +72,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
||||
}
|
||||
button.setOnClickListener(v -> {
|
||||
try {
|
||||
Setting<?> setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
||||
String defaultStringValue = setting.defaultValue.toString();
|
||||
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
|
||||
EditText editText = getEditText();
|
||||
editText.setText(defaultStringValue);
|
||||
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -122,6 +122,21 @@ public class SpoofVideoStreamsPatch {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -204,7 +204,7 @@ public class StreamingDataRequest {
|
||||
// but empty response body does.
|
||||
if (connection.getContentLength() == 0) {
|
||||
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 {
|
||||
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||
|
||||
16
extensions/spotify/build.gradle.kts
Normal file
16
extensions/spotify/build.gradle.kts
Normal 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
|
||||
}
|
||||
}
|
||||
1
extensions/spotify/src/main/AndroidManifest.xml
Normal file
1
extensions/spotify/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/spotify/stub/build.gradle.kts
Normal file
17
extensions/spotify/stub/build.gradle.kts
Normal 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
|
||||
}
|
||||
}
|
||||
1
extensions/spotify/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/spotify/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -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_;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.spotify.remoteconfig.internal;
|
||||
|
||||
public final class AccountAttribute {
|
||||
public Object value_;
|
||||
}
|
||||
@@ -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_;
|
||||
}
|
||||
@@ -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.FeedItemList;
|
||||
import com.ss.android.ugc.aweme.follow.presenter.FollowFeedList;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -13,22 +14,41 @@ public final class FeedItemsFilter {
|
||||
new StoryFilter(),
|
||||
new ImageVideoFilter(),
|
||||
new ViewCountFilter(),
|
||||
new LikeCountFilter()
|
||||
new LikeCountFilter(),
|
||||
new ShopFilter()
|
||||
);
|
||||
|
||||
public static void filter(FeedItemList feedItemList) {
|
||||
Iterator<Aweme> feedItemListIterator = feedItemList.items.iterator();
|
||||
while (feedItemListIterator.hasNext()) {
|
||||
Aweme item = feedItemListIterator.next();
|
||||
if (item == null) continue;
|
||||
filterFeedList(feedItemList.items, item -> item);
|
||||
}
|
||||
|
||||
for (IFilter filter : FILTERS) {
|
||||
boolean enabled = filter.getEnabled();
|
||||
if (enabled && filter.getFiltered(item)) {
|
||||
feedItemListIterator.remove();
|
||||
break;
|
||||
}
|
||||
public static void filter(FollowFeedList followFeedList) {
|
||||
filterFeedList(followFeedList.mItems, feed -> (feed != null) ? feed.aweme : null);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import app.revanced.extension.shared.settings.StringSetting;
|
||||
public class Settings extends BaseSettings {
|
||||
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_SHOP = new BooleanSetting("hide_shop", 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 StringSetting MIN_MAX_VIEWS = new StringSetting("min_max_views", "0-" + Long.MAX_VALUE, true);
|
||||
|
||||
@@ -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.ExtensionPreferenceCategory;
|
||||
import app.revanced.extension.tiktok.settings.preference.categories.SimSpoofPreferenceCategory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Preference fragment for ReVanced settings
|
||||
|
||||
@@ -26,6 +26,11 @@ public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory
|
||||
"Remove feed ads", "Remove ads from feed.",
|
||||
Settings.REMOVE_ADS
|
||||
));
|
||||
addPreference(new TogglePreference(
|
||||
context,
|
||||
"Hide TikTok Shop", "Hide TikTok shop from feed.",
|
||||
Settings.HIDE_SHOP
|
||||
));
|
||||
addPreference(new TogglePreference(
|
||||
context,
|
||||
"Hide livestreams", "Hide livestreams from feed.",
|
||||
|
||||
@@ -33,4 +33,8 @@ public class Aweme {
|
||||
public AwemeStatistics getStatistics() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
|
||||
public String getShareUrl() {
|
||||
throw new UnsupportedOperationException("Stub");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -163,7 +163,7 @@ internal object TwiFucker {
|
||||
|
||||
private fun JSONObject.entryIsWhoToFollow(): Boolean =
|
||||
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 =
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
package app.revanced.extension.youtube;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
import static app.revanced.extension.shared.Utils.clamp;
|
||||
|
||||
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 app.revanced.extension.shared.Logger;
|
||||
@@ -45,13 +55,24 @@ public class ThemeHelper {
|
||||
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),
|
||||
* or the dark mode background color unpatched YT uses.
|
||||
*/
|
||||
public static int getDarkThemeColor() {
|
||||
if (darkThemeColor == null) {
|
||||
darkThemeColor = getColorInt(darkThemeResourceName());
|
||||
darkThemeColor = getThemeColor(darkThemeResourceName(), Color.BLACK);
|
||||
}
|
||||
return darkThemeColor;
|
||||
}
|
||||
@@ -71,18 +92,11 @@ public class ThemeHelper {
|
||||
*/
|
||||
public static int getLightThemeColor() {
|
||||
if (lightThemeColor == null) {
|
||||
lightThemeColor = getColorInt(lightThemeResourceName());
|
||||
lightThemeColor = getThemeColor(lightThemeResourceName(), Color.WHITE);
|
||||
}
|
||||
return lightThemeColor;
|
||||
}
|
||||
|
||||
private static int getColorInt(String colorString) {
|
||||
if (colorString.startsWith("#")) {
|
||||
return Color.parseColor(colorString);
|
||||
}
|
||||
return Utils.getResourceColor(colorString);
|
||||
}
|
||||
|
||||
public static int getBackgroundColor() {
|
||||
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
|
||||
}
|
||||
@@ -96,6 +110,62 @@ public class ThemeHelper {
|
||||
? "yt_black3"
|
||||
: "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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -23,7 +24,13 @@ public class BackgroundPlaybackPatch {
|
||||
// 7. Close the Short
|
||||
// 8. Resume playing the regular video
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@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.
|
||||
* <p>
|
||||
@@ -93,6 +101,8 @@ public final class ChangeStartPagePatch {
|
||||
|
||||
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.
|
||||
* 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;
|
||||
}
|
||||
|
||||
if (appLaunched) {
|
||||
if (!CHANGE_START_PAGE_ALWAYS && appLaunched) {
|
||||
Logger.printDebug(() -> "Ignore override browseId as the app already launched");
|
||||
return original;
|
||||
}
|
||||
appLaunched = true;
|
||||
|
||||
Logger.printDebug(() -> "Changing browseId to " + START_PAGE.id);
|
||||
Logger.printDebug(() -> "Changing browseId to: " + START_PAGE.id);
|
||||
return START_PAGE.id;
|
||||
}
|
||||
|
||||
@@ -125,14 +135,14 @@ public final class ChangeStartPagePatch {
|
||||
return;
|
||||
}
|
||||
|
||||
if (appLaunched) {
|
||||
if (!CHANGE_START_PAGE_ALWAYS && appLaunched) {
|
||||
Logger.printDebug(() -> "Ignore override intent action as the app already launched");
|
||||
return;
|
||||
}
|
||||
appLaunched = true;
|
||||
|
||||
String intentAction = START_PAGE.id;
|
||||
Logger.printDebug(() -> "Changing intent action to " + intentAction);
|
||||
Logger.printDebug(() -> "Changing intent action to: " + intentAction);
|
||||
intent.setAction(intentAction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ public class CustomPlayerOverlayOpacityPatch {
|
||||
|
||||
if (opacity < 0 || opacity > 100) {
|
||||
Utils.showToastLong(str("revanced_player_overlay_opacity_invalid_toast"));
|
||||
Settings.PLAYER_OVERLAY_OPACITY.resetToDefault();
|
||||
opacity = Settings.PLAYER_OVERLAY_OPACITY.defaultValue;
|
||||
opacity = Settings.PLAYER_OVERLAY_OPACITY.resetToDefault();
|
||||
}
|
||||
|
||||
PLAYER_OVERLAY_OPACITY_LEVEL = (opacity * 255) / 100;
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class DisableAutoCaptionsPatch {
|
||||
|
||||
/**
|
||||
* Used by injected code. Do not delete.
|
||||
*/
|
||||
public static boolean captionsButtonDisabled;
|
||||
private static volatile boolean captionsButtonStatus;
|
||||
|
||||
public static boolean autoCaptionsEnabled() {
|
||||
return Settings.AUTO_CAPTIONS.get()
|
||||
// Do not use auto captions for Shorts.
|
||||
&& ShortsPlayerState.isOpen();
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean disableAutoCaptions() {
|
||||
return Settings.DISABLE_AUTO_CAPTIONS.get() && !captionsButtonStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setCaptionsButtonStatus(boolean status) {
|
||||
captionsButtonStatus = status;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package app.revanced.extension.youtube.patches;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/** @noinspection unused*/
|
||||
@SuppressWarnings("unused")
|
||||
public class DisableResumingStartupShortsPlayerPatch {
|
||||
|
||||
/**
|
||||
@@ -11,4 +11,11 @@ public class DisableResumingStartupShortsPlayerPatch {
|
||||
public static boolean disableResumingStartupShortsPlayer() {
|
||||
return Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean disableResumingStartupShortsPlayer(boolean original) {
|
||||
return original && !Settings.DISABLE_RESUMING_SHORTS_PLAYER.get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -43,10 +43,13 @@ public final class MiniplayerPatch {
|
||||
MODERN_2(null, 2),
|
||||
MODERN_3(null, 3),
|
||||
/**
|
||||
* Half broken miniplayer, that might be work in progress or left over abandoned code.
|
||||
* Can force this type by editing the import/export settings.
|
||||
* Works and is functional with 20.03+
|
||||
*/
|
||||
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.
|
||||
@@ -126,12 +129,13 @@ public final class MiniplayerPatch {
|
||||
private static final boolean DRAG_AND_DROP_ENABLED =
|
||||
CURRENT_TYPE.isModern() && Settings.MINIPLAYER_DRAG_AND_DROP.get();
|
||||
|
||||
private static final boolean HIDE_EXPAND_CLOSE_ENABLED =
|
||||
Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.get()
|
||||
&& Settings.MINIPLAYER_HIDE_EXPAND_CLOSE.isAvailable();
|
||||
private static final boolean HIDE_OVERLAY_BUTTONS_ENABLED =
|
||||
Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.get()
|
||||
&& Settings.MINIPLAYER_HIDE_OVERLAY_BUTTONS.isAvailable();
|
||||
|
||||
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,
|
||||
// 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());
|
||||
|
||||
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 =
|
||||
DRAG_AND_DROP_ENABLED && Settings.MINIPLAYER_HORIZONTAL_DRAG.get();
|
||||
@@ -158,8 +162,7 @@ public final class MiniplayerPatch {
|
||||
|
||||
if (opacity < 0 || opacity > 100) {
|
||||
Utils.showToastLong(str("revanced_miniplayer_opacity_invalid_toast"));
|
||||
Settings.MINIPLAYER_OPACITY.resetToDefault();
|
||||
opacity = Settings.MINIPLAYER_OPACITY.defaultValue;
|
||||
opacity = Settings.MINIPLAYER_OPACITY.resetToDefault();
|
||||
}
|
||||
|
||||
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
|
||||
public boolean isAvailable() {
|
||||
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
|
||||
&& !Settings.MINIPLAYER_DOUBLE_TAP_ACTION.get() && !Settings.MINIPLAYER_DRAG_AND_DROP.get())
|
||||
|| (IS_19_29_OR_GREATER && type == MODERN_3);
|
||||
@@ -227,9 +231,13 @@ public final class MiniplayerPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void adjustMiniplayerOpacity(ImageView view) {
|
||||
public static void adjustMiniplayerOpacity(View view) {
|
||||
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.
|
||||
*/
|
||||
public static boolean enableMiniplayerDoubleTapAction(boolean original) {
|
||||
public static boolean getMiniplayerDoubleTapAction(boolean original) {
|
||||
if (CURRENT_TYPE == DEFAULT) {
|
||||
return original;
|
||||
}
|
||||
@@ -258,7 +266,7 @@ public final class MiniplayerPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean enableMiniplayerDragAndDrop(boolean original) {
|
||||
public static boolean getMiniplayerDragAndDrop(boolean original) {
|
||||
if (CURRENT_TYPE == DEFAULT) {
|
||||
return original;
|
||||
}
|
||||
@@ -266,13 +274,36 @@ public final class MiniplayerPatch {
|
||||
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.
|
||||
*/
|
||||
public static boolean setRoundedCorners(boolean original) {
|
||||
if (CURRENT_TYPE.isModern()) {
|
||||
return MINIPLAYER_ROUNDED_CORNERS_ENABLED;
|
||||
public static boolean getHorizontalDrag(boolean original) {
|
||||
if (CURRENT_TYPE == DEFAULT) {
|
||||
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;
|
||||
@@ -281,7 +312,7 @@ public final class MiniplayerPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int setMiniplayerDefaultSize(int original) {
|
||||
public static int getMiniplayerDefaultSize(int original) {
|
||||
if (CURRENT_TYPE.isModern()) {
|
||||
return MINIPLAYER_SIZE;
|
||||
}
|
||||
@@ -289,29 +320,26 @@ public final class MiniplayerPatch {
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void hideMiniplayerExpandClose(View view) {
|
||||
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_OVERLAY_BUTTONS_ENABLED, view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean setHorizontalDrag(boolean original) {
|
||||
if (CURRENT_TYPE.isModern()) {
|
||||
return MINIPLAYER_HORIZONTAL_DRAG_ENABLED;
|
||||
public static void hideMiniplayerActionButton(View view) {
|
||||
if (CURRENT_TYPE == MODERN_4) {
|
||||
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_OVERLAY_BUTTONS_ENABLED, view);
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void hideMiniplayerExpandClose(ImageView view) {
|
||||
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_EXPAND_CLOSE_ENABLED, view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void hideMiniplayerRewindForward(ImageView view) {
|
||||
public static void hideMiniplayerRewindForward(View view) {
|
||||
Utils.hideViewByRemovingFromParentUnderCondition(HIDE_REWIND_FORWARD_ENABLED, view);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,28 +2,22 @@ package app.revanced.extension.youtube.patches;
|
||||
|
||||
import static app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike.Vote;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ShapeDrawable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
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.requests.ReturnYouTubeDislikeApi;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
||||
@@ -47,9 +41,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
||||
@SuppressWarnings("unused")
|
||||
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.
|
||||
*/
|
||||
@@ -64,12 +55,12 @@ public class ReturnYouTubeDislikePatch {
|
||||
private static volatile ReturnYouTubeDislike lastLithoShortsVideoData;
|
||||
|
||||
/**
|
||||
* Because the litho Shorts spans are created after {@link ReturnYouTubeDislikeFilterPatch}
|
||||
* detects the video ids, after the user votes the litho will update
|
||||
* but {@link #lastLithoShortsVideoData} is not the correct data to use.
|
||||
* If this is true, then instead use {@link #currentVideoData}.
|
||||
* Because litho Shorts spans are created offscreen after {@link ReturnYouTubeDislikeFilterPatch}
|
||||
* detects the video ids, but the current Short can arbitrarily reload the same span,
|
||||
* then use the {@link #lastLithoShortsVideoData} if this value is greater than zero.
|
||||
*/
|
||||
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.
|
||||
@@ -77,22 +68,31 @@ public class ReturnYouTubeDislikePatch {
|
||||
@Nullable
|
||||
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() {
|
||||
currentVideoData = null;
|
||||
lastLithoShortsVideoData = null;
|
||||
lithoShortsShouldUseCurrentData = false;
|
||||
synchronized (ReturnYouTubeDislike.class) {
|
||||
useLithoShortsVideoDataCount = 0;
|
||||
}
|
||||
|
||||
// Rolling number text should not be cleared,
|
||||
// as it's used if incognito Short is opened/closed
|
||||
// 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.
|
||||
@@ -156,10 +156,13 @@ public class ReturnYouTubeDislikePatch {
|
||||
return getShortsSpan(original, true);
|
||||
}
|
||||
|
||||
if (conversionContextString.contains("|shorts_like_button.eml")
|
||||
&& !Utils.containsNumber(original)) {
|
||||
Logger.printDebug(() -> "Replacing hidden likes count");
|
||||
return getShortsSpan(original, false);
|
||||
if (conversionContextString.contains("|shorts_like_button.eml")) {
|
||||
if (!Utils.containsNumber(original)) {
|
||||
Logger.printDebug(() -> "Replacing hidden likes count");
|
||||
return getShortsSpan(original, false);
|
||||
} else {
|
||||
decrementUseLithoDataIfNeeded();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onLithoTextLoaded failure", ex);
|
||||
@@ -174,7 +177,14 @@ public class ReturnYouTubeDislikePatch {
|
||||
return original;
|
||||
}
|
||||
|
||||
ReturnYouTubeDislike videoData = lastLithoShortsVideoData;
|
||||
final ReturnYouTubeDislike videoData;
|
||||
if (decrementUseLithoDataIfNeeded()) {
|
||||
// New Short is loading off screen.
|
||||
videoData = lastLithoShortsVideoData;
|
||||
} else {
|
||||
videoData = currentVideoData;
|
||||
}
|
||||
|
||||
if (videoData == null) {
|
||||
// The Shorts litho video id filter did not detect the video id.
|
||||
// This is normal in incognito mode, but otherwise is abnormal.
|
||||
@@ -182,19 +192,6 @@ public class ReturnYouTubeDislikePatch {
|
||||
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
|
||||
? videoData.getDislikeSpanForShort((Spanned) original)
|
||||
: videoData.getLikeSpanForShort((Spanned) original);
|
||||
@@ -269,7 +266,7 @@ public class ReturnYouTubeDislikePatch {
|
||||
Logger.printDebug(() -> "Adding rolling number TextView changes");
|
||||
view.setCompoundDrawablePadding(ReturnYouTubeDislike.leftSeparatorShapePaddingPixels);
|
||||
ShapeDrawable separator = ReturnYouTubeDislike.getLeftSeparatorDrawable();
|
||||
if (Utils.isRightToLeftTextLayout()) {
|
||||
if (Utils.isRightToLeftLocale()) {
|
||||
view.setCompoundDrawables(null, null, separator, null);
|
||||
} else {
|
||||
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).
|
||||
//
|
||||
@@ -503,8 +369,7 @@ public class ReturnYouTubeDislikePatch {
|
||||
if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get())) {
|
||||
return;
|
||||
}
|
||||
final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
||||
&& videoIdIsShort && !lastPlayerResponseWasShort;
|
||||
final boolean waitForFetchToComplete = videoIdIsShort && !lastPlayerResponseWasShort;
|
||||
|
||||
Logger.printDebug(() -> "Prefetching RYD for video: " + videoId);
|
||||
ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||
@@ -557,12 +422,6 @@ public class ReturnYouTubeDislikePatch {
|
||||
data.setVideoIdIsShort(true);
|
||||
}
|
||||
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) {
|
||||
Logger.printException(() -> "newVideoLoaded failure", ex);
|
||||
}
|
||||
@@ -587,7 +446,10 @@ public class ReturnYouTubeDislikePatch {
|
||||
ReturnYouTubeDislike videoData = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||
videoData.setVideoIdIsShort(true);
|
||||
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) {
|
||||
@@ -622,13 +484,6 @@ public class ReturnYouTubeDislikePatch {
|
||||
for (Vote v : Vote.values()) {
|
||||
if (v.value == vote) {
|
||||
videoData.sendVote(v);
|
||||
|
||||
if (isNoneHiddenOrMinimized) {
|
||||
if (lastLithoShortsVideoData != null) {
|
||||
lithoShortsShouldUseCurrentData = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package app.revanced.extension.youtube.patches;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
|
||||
@@ -76,7 +78,7 @@ public class ShortsAutoplayPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) {
|
||||
public static Enum<?> changeShortsRepeatBehavior(@Nullable Enum<?> original) {
|
||||
try {
|
||||
final boolean autoplay;
|
||||
|
||||
@@ -98,17 +100,35 @@ public class ShortsAutoplayPatch {
|
||||
: ShortsLoopBehavior.REPEAT;
|
||||
|
||||
if (behavior.ytEnumValue != null) {
|
||||
Logger.printDebug(() -> behavior.ytEnumValue == original
|
||||
? "Changing Shorts repeat behavior from: " + original.name() + " to: " + behavior.ytEnumValue
|
||||
: "Behavior setting is same as original. Using original: " + original.name()
|
||||
);
|
||||
Logger.printDebug(() -> {
|
||||
String name = (original == null ? "unknown (null)" : 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
Logger.printException(() -> "changeShortsRepeatState failure", ex);
|
||||
Logger.printException(() -> "changeShortsRepeatBehavior failure", ex);
|
||||
}
|
||||
|
||||
return original;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean isAutoPlay(Enum<?> original) {
|
||||
return ShortsLoopBehavior.SINGLE_PLAY.ytEnumValue == original;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@ package app.revanced.extension.youtube.patches;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
|
||||
public class VersionCheckPatch {
|
||||
public static final boolean IS_19_17_OR_GREATER = Utils.getAppVersionName().compareTo("19.17.00") >= 0;
|
||||
public static final boolean IS_19_20_OR_GREATER = Utils.getAppVersionName().compareTo("19.20.00") >= 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_34_OR_GREATER = Utils.getAppVersionName().compareTo("19.34.00") >= 0;
|
||||
public static final boolean IS_19_46_OR_GREATER = Utils.getAppVersionName().compareTo("19.46.00") >= 0;
|
||||
private static boolean isVersionOrGreater(String version) {
|
||||
return Utils.getAppVersionName().compareTo(version) >= 0;
|
||||
}
|
||||
|
||||
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
|
||||
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -1,11 +1,48 @@
|
||||
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;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class WideSearchbarPatch {
|
||||
|
||||
private static final Boolean WIDE_SEARCHBAR_ENABLED = Settings.WIDE_SEARCHBAR.get();
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,10 +177,7 @@ public final class AdsFilter extends Filter {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == playerShoppingShelf) {
|
||||
if (contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
return contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
// Check for the index because of likelihood of false positives.
|
||||
@@ -198,13 +195,10 @@ public final class AdsFilter extends Filter {
|
||||
}
|
||||
|
||||
if (matchedGroup == channelProfile) {
|
||||
if (visitStoreButton.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
return visitStoreButton.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,7 @@ final class ButtonsFilter extends Filter {
|
||||
|
||||
bufferFilterPathGroup = new StringFilterGroup(
|
||||
null,
|
||||
"|ContainerType|button.eml|"
|
||||
"|ContainerType|button.eml"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
@@ -43,7 +43,7 @@ final class ButtonsFilter extends Filter {
|
||||
),
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_DOWNLOAD_BUTTON,
|
||||
"|download_button.eml|"
|
||||
"|download_button.eml"
|
||||
),
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_PLAYLIST_BUTTON,
|
||||
@@ -51,7 +51,7 @@ final class ButtonsFilter extends Filter {
|
||||
),
|
||||
new StringFilterGroup(
|
||||
Settings.HIDE_CLIP_BUTTON,
|
||||
"|clip_button.eml|"
|
||||
"|clip_button.eml"
|
||||
)
|
||||
);
|
||||
|
||||
@@ -68,15 +68,19 @@ final class ButtonsFilter extends Filter {
|
||||
Settings.HIDE_REMIX_BUTTON,
|
||||
"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,
|
||||
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_CLIP_BUTTON,
|
||||
"yt_outline_scissors"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_THANKS_BUTTON,
|
||||
"yt_outline_dollar_sign_heart"
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -95,29 +99,23 @@ final class ButtonsFilter extends Filter {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == likeSubscribeGlow) {
|
||||
if ((path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
||||
&& path.contains(ANIMATED_VECTOR_TYPE_PATH)) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
||||
&& path.contains(ANIMATED_VECTOR_TYPE_PATH);
|
||||
}
|
||||
|
||||
// If the current matched group is the action bar group,
|
||||
// in case every filter group is enabled, hide the action bar.
|
||||
if (matchedGroup == actionBarGroup) {
|
||||
if (!isEveryFilterGroupEnabled()) {
|
||||
return false;
|
||||
}
|
||||
} else if (matchedGroup == bufferFilterPathGroup) {
|
||||
// Make sure the current path is the right one
|
||||
// to avoid false positives.
|
||||
if (!path.startsWith(VIDEO_ACTION_BAR_PATH)) return false;
|
||||
|
||||
// In case the group list has no match, return false.
|
||||
if (!bufferButtonsGroupList.check(protobufBufferArray).isFiltered()) return false;
|
||||
return isEveryFilterGroupEnabled();
|
||||
}
|
||||
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
if (matchedGroup == bufferFilterPathGroup) {
|
||||
// Make sure the current path is the right one
|
||||
// to avoid false positives.
|
||||
return path.startsWith(VIDEO_ACTION_BAR_PATH)
|
||||
&& bufferButtonsGroupList.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@ final class CommentsFilter extends Filter {
|
||||
|
||||
private final StringFilterGroup commentComposer;
|
||||
private final ByteArrayFilterGroup emojiPickerBufferGroup;
|
||||
private final StringFilterGroup filterChipBar;
|
||||
private final ByteArrayFilterGroup aiCommentsSummary;
|
||||
|
||||
public CommentsFilter() {
|
||||
var chatSummary = new StringFilterGroup(
|
||||
Settings.HIDE_COMMENTS_CHAT_SUMMARY,
|
||||
Settings.HIDE_COMMENTS_AI_CHAT_SUMMARY,
|
||||
"live_chat_summary_banner.eml"
|
||||
);
|
||||
|
||||
@@ -58,6 +60,16 @@ final class CommentsFilter extends Filter {
|
||||
"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(
|
||||
chatSummary,
|
||||
commentsByMembers,
|
||||
@@ -65,7 +77,8 @@ final class CommentsFilter extends Filter {
|
||||
createAShort,
|
||||
previewComment,
|
||||
thanksButton,
|
||||
commentComposer
|
||||
commentComposer,
|
||||
filterChipBar
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,15 +88,15 @@ final class CommentsFilter extends Filter {
|
||||
if (matchedGroup == commentComposer) {
|
||||
// To completely hide the emoji buttons (and leave no empty space), the timestamp button is
|
||||
// also hidden because the buffer is exactly the same and there's no way selectively hide.
|
||||
if (contentIndex == 0
|
||||
return contentIndex == 0
|
||||
&& path.endsWith(TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH)
|
||||
&& emojiPickerBufferGroup.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
&& emojiPickerBufferGroup.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
if (matchedGroup == filterChipBar) {
|
||||
return aiCommentsSummary.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,9 +153,11 @@ final class CustomFilter extends Filter {
|
||||
if (custom.startsWith && contentIndex != 0) {
|
||||
return false;
|
||||
}
|
||||
if (custom.bufferSearch != null && !custom.bufferSearch.matches(protobufBufferArray)) {
|
||||
return false;
|
||||
|
||||
if (custom.bufferSearch == null) {
|
||||
return true; // No buffer filter, only path filtering.
|
||||
}
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
|
||||
return custom.bufferSearch.matches(protobufBufferArray);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,16 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
"metadata"
|
||||
);
|
||||
|
||||
final StringFilterGroup aiGeneratedVideoSummarySection = new StringFilterGroup(
|
||||
Settings.HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION,
|
||||
"cell_expandable_metadata.eml"
|
||||
);
|
||||
|
||||
final StringFilterGroup askSection = new StringFilterGroup(
|
||||
Settings.HIDE_ASK_SECTION,
|
||||
"youchat_entrypoint.eml"
|
||||
);
|
||||
|
||||
final StringFilterGroup attributesSection = new StringFilterGroup(
|
||||
Settings.HIDE_ATTRIBUTES_SECTION,
|
||||
"gaming_section",
|
||||
@@ -67,6 +77,8 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
aiGeneratedVideoSummarySection,
|
||||
askSection,
|
||||
attributesSection,
|
||||
infoCardsSection,
|
||||
howThisWasMadeSection,
|
||||
@@ -82,13 +94,9 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
if (exceptions.matches(path)) return false;
|
||||
|
||||
if (matchedGroup == macroMarkersCarousel) {
|
||||
if (contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
return contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
|
||||
/**
|
||||
* Filters litho based components.
|
||||
*
|
||||
@@ -62,10 +59,7 @@ abstract class Filter {
|
||||
* Called after an enabled filter has been matched.
|
||||
* Default implementation is to always filter the matched component and log the action.
|
||||
* Subclasses can perform additional or different checks if needed.
|
||||
* <p>
|
||||
* If the content is to be filtered, subclasses should always
|
||||
* call this method (and never return a plain 'true').
|
||||
* That way the logs will always show when a component was filtered and which filter hide it.
|
||||
*
|
||||
* <p>
|
||||
* Method is called off the main thread.
|
||||
*
|
||||
@@ -76,14 +70,6 @@ abstract class Filter {
|
||||
*/
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (BaseSettings.DEBUG.get()) {
|
||||
String filterSimpleName = getClass().getSimpleName();
|
||||
if (contentType == FilterContentType.IDENTIFIER) {
|
||||
Logger.printDebug(() -> filterSimpleName + " Filtered identifier: " + identifier);
|
||||
} else {
|
||||
Logger.printDebug(() -> filterSimpleName + " Filtered path: " + path);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +576,7 @@ final class KeywordContentFilter extends Filter {
|
||||
MutableReference<String> matchRef = new MutableReference<>();
|
||||
if (bufferSearch.matches(protobufBufferArray, matchRef)) {
|
||||
updateStats(true, matchRef.value);
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
updateStats(false, null);
|
||||
|
||||
@@ -34,12 +34,11 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
private final StringFilterGroup notifyMe;
|
||||
private final StringFilterGroup singleItemInformationPanel;
|
||||
private final StringFilterGroup expandableMetadata;
|
||||
private final ByteArrayFilterGroup searchResultRecommendations;
|
||||
private final StringFilterGroup searchResultVideo;
|
||||
private final StringFilterGroup compactChannelBarInner;
|
||||
private final StringFilterGroup compactChannelBarInnerButton;
|
||||
private final ByteArrayFilterGroup joinMembershipButton;
|
||||
private final StringFilterGroup horizontalShelves;
|
||||
private final ByteArrayFilterGroup ticketShelf;
|
||||
|
||||
public LayoutComponentsFilter() {
|
||||
exceptions.addPatterns(
|
||||
@@ -75,7 +74,10 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
"post_base_wrapper_slim.eml",
|
||||
"poll_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(
|
||||
@@ -211,7 +213,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
|
||||
compactChannelBarInnerButton = new StringFilterGroup(
|
||||
null,
|
||||
"|button.eml|"
|
||||
"|button.eml"
|
||||
);
|
||||
|
||||
joinMembershipButton = new ByteArrayFilterGroup(
|
||||
@@ -230,14 +232,9 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
"mixed_content_shelf"
|
||||
);
|
||||
|
||||
searchResultVideo = new StringFilterGroup(
|
||||
Settings.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
|
||||
"search_video_with_context.eml"
|
||||
);
|
||||
|
||||
searchResultRecommendations = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SEARCH_RESULT_RECOMMENDATIONS,
|
||||
"endorsement_header_footer"
|
||||
final var searchResultRecommendationLabels = new StringFilterGroup(
|
||||
Settings.HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS,
|
||||
"endorsement_header_footer.eml"
|
||||
);
|
||||
|
||||
horizontalShelves = new StringFilterGroup(
|
||||
@@ -248,6 +245,11 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
"horizontal_tile_shelf.eml"
|
||||
);
|
||||
|
||||
ticketShelf = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_TICKET_SHELF,
|
||||
"ticket"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
expandableMetadata,
|
||||
inFeedSurvey,
|
||||
@@ -255,7 +257,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
compactChannelBar,
|
||||
communityPosts,
|
||||
paidPromotion,
|
||||
searchResultVideo,
|
||||
searchResultRecommendationLabels,
|
||||
latestPosts,
|
||||
channelWatermark,
|
||||
communityGuidelines,
|
||||
@@ -290,50 +292,29 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
// From 2025, the medical information panel is no longer shown in the search results.
|
||||
// Therefore, this identifier does not filter when the search bar is activated.
|
||||
if (matchedGroup == singleItemInformationPanel) {
|
||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen() || !NavigationBar.isSearchBarActive()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matchedGroup == searchResultVideo) {
|
||||
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
return PlayerType.getCurrent().isMaximizedOrFullscreen() || !NavigationBar.isSearchBarActive();
|
||||
}
|
||||
|
||||
// The groups are excluded from the filter due to the exceptions list below.
|
||||
// Filter them separately here.
|
||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
|
||||
{
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
||||
|
||||
if (matchedGroup == compactChannelBarInner) {
|
||||
if (compactChannelBarInnerButton.check(path).isFiltered()) {
|
||||
// The filter may be broad, but in the context of a compactChannelBarInnerButton,
|
||||
// it's safe to assume that the button is the only thing that should be hidden.
|
||||
if (joinMembershipButton.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
return compactChannelBarInnerButton.check(path).isFiltered()
|
||||
// The filter may be broad, but in the context of a compactChannelBarInnerButton,
|
||||
// it's safe to assume that the button is the only thing that should be hidden.
|
||||
&& joinMembershipButton.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
if (matchedGroup == horizontalShelves) {
|
||||
if (contentIndex == 0 && hideShelves()) {
|
||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
return contentIndex == 0 && (hideShelves() || ticketShelf.check(protobufBufferArray).isFiltered());
|
||||
}
|
||||
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -454,16 +435,20 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
}
|
||||
|
||||
private static boolean hideShelves() {
|
||||
// If the player is opened while library is selected,
|
||||
// then filter any recommendations below the player.
|
||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||
// Or if the search is active while library is selected, then also filter.
|
||||
|| NavigationBar.isSearchBarActive()) {
|
||||
// Horizontal shelves are used for music/game links in video descriptions,
|
||||
// such as https://youtube.com/watch?v=W8kI1na3S2M
|
||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Must check search bar after player type, since search results
|
||||
// can be in the background behind an open player.
|
||||
if (NavigationBar.isSearchBarActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not hide if the navigation back button is visible,
|
||||
// otherwise the content shelves in the YouTube Movie/Courses pages is hidden.
|
||||
// otherwise the content shelves in the explore/music/courses pages are hidde.
|
||||
if (NavigationBar.isBackButtonVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.youtube.StringTrieSearch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
@@ -87,6 +88,10 @@ public final class LithoFilterPatch {
|
||||
* 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<>();
|
||||
/**
|
||||
* Results of calling {@link #filter(String, StringBuilder)}.
|
||||
*/
|
||||
private static final ThreadLocal<Boolean> filterResult = new ThreadLocal<>();
|
||||
|
||||
static {
|
||||
for (Filter filter : filters) {
|
||||
@@ -110,12 +115,29 @@ public final class LithoFilterPatch {
|
||||
if (!group.includeInSearch()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (String pattern : group.filters) {
|
||||
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> {
|
||||
String filterSimpleName = filter.getClass().getSimpleName();
|
||||
|
||||
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex,
|
||||
matchedLength, callbackParameter) -> {
|
||||
if (!group.isEnabled()) return false;
|
||||
|
||||
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
|
||||
return filter.isFiltered(parameters.identifier, parameters.path, parameters.protoBuffer,
|
||||
group, type, matchedStartIndex);
|
||||
final boolean isFiltered = filter.isFiltered(parameters.identifier,
|
||||
parameters.path, parameters.protoBuffer, group, type, matchedStartIndex);
|
||||
|
||||
if (isFiltered && BaseSettings.DEBUG.get()) {
|
||||
if (type == Filter.FilterContentType.IDENTIFIER) {
|
||||
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
||||
+ " identifier: " + parameters.identifier);
|
||||
} else {
|
||||
Logger.printDebug(() -> "Filtered " + filterSimpleName
|
||||
+ " path: " + parameters.path);
|
||||
}
|
||||
}
|
||||
|
||||
return isFiltered;
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -140,11 +162,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.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static boolean filter(@Nullable String lithoIdentifier, @NonNull StringBuilder pathBuilder) {
|
||||
public static void filter(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
|
||||
filterResult.set(handleFiltering(lithoIdentifier, pathBuilder));
|
||||
}
|
||||
|
||||
private static boolean handleFiltering(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
|
||||
try {
|
||||
if (pathBuilder.length() == 0) {
|
||||
return false;
|
||||
|
||||
@@ -2,12 +2,25 @@ package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
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.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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 ByteArrayFilterGroup exception;
|
||||
@@ -27,7 +40,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
|
||||
addPathCallbacks(
|
||||
videoQualityMenuFooter,
|
||||
new StringFilterGroup(null, "overflow_menu_item.eml|")
|
||||
new StringFilterGroup(null, "overflow_menu_item.eml")
|
||||
);
|
||||
|
||||
flyoutFilterGroupList.addAll(
|
||||
@@ -86,7 +99,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == videoQualityMenuFooter) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contentIndex != 0) {
|
||||
@@ -98,11 +111,6 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
// Super class handles logging.
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
return flyoutFilterGroupList.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
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 android.view.View;
|
||||
@@ -52,6 +51,7 @@ public final class ShortsFilter extends Filter {
|
||||
private final StringFilterGroup suggestedAction;
|
||||
private final ByteArrayFilterGroupList suggestedActionsGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
private final StringFilterGroup shortsActionBar;
|
||||
private final StringFilterGroup actionButton;
|
||||
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
@@ -141,6 +141,16 @@ public final class ShortsFilter extends Filter {
|
||||
"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(
|
||||
Settings.HIDE_SHORTS_JOIN_BUTTON,
|
||||
"sponsor_button"
|
||||
@@ -156,9 +166,15 @@ public final class ShortsFilter extends Filter {
|
||||
"reel_player_disclosure.eml"
|
||||
);
|
||||
|
||||
shortsActionBar = new StringFilterGroup(
|
||||
null,
|
||||
"shorts_action_bar.eml"
|
||||
);
|
||||
|
||||
actionButton = new StringFilterGroup(
|
||||
null,
|
||||
"shorts_video_action_button.eml"
|
||||
// Can be simply 'button.eml' or 'shorts_video_action_button.eml'
|
||||
"button.eml"
|
||||
);
|
||||
|
||||
suggestedAction = new StringFilterGroup(
|
||||
@@ -167,27 +183,16 @@ public final class ShortsFilter extends Filter {
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
shortsCompactFeedVideoPath, suggestedAction, actionButton, joinButton, subscribeButton,
|
||||
paidPromotionButton, pausedOverlayButtons, channelBar, fullVideoLinkLabel, videoTitle,
|
||||
reelSoundMetadata, soundButton, infoPanel, stickers, likeFountain
|
||||
shortsCompactFeedVideoPath, joinButton, subscribeButton, paidPromotionButton,
|
||||
shortsActionBar, suggestedAction, pausedOverlayButtons, channelBar,
|
||||
fullVideoLinkLabel, videoTitle, reelSoundMetadata, soundButton, infoPanel,
|
||||
stickers, likeFountain, likeButton, dislikeButton
|
||||
);
|
||||
|
||||
//
|
||||
// Action buttons
|
||||
// All other action buttons.
|
||||
//
|
||||
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(
|
||||
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
|
||||
"reel_comment_button"
|
||||
@@ -273,25 +278,18 @@ public final class ShortsFilter extends Filter {
|
||||
if (contentType == FilterContentType.PATH) {
|
||||
if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) {
|
||||
// Selectively filter to avoid false positive filtering of other subscribe/join buttons.
|
||||
if (path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH)) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
return path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH);
|
||||
}
|
||||
|
||||
if (matchedGroup == shortsCompactFeedVideoPath) {
|
||||
if (shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
// Video action buttons (like, dislike, comment, share, remix) have the same path.
|
||||
if (matchedGroup == actionButton) {
|
||||
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
// Video action buttons (comment, share, remix) have the same path.
|
||||
// Like and dislike are separate path filters and don't require buffer searching.
|
||||
if (matchedGroup == shortsActionBar) {
|
||||
return actionButton.check(path).isFiltered()
|
||||
&& videoActionButtonGroupList.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
if (matchedGroup == suggestedAction) {
|
||||
@@ -299,28 +297,23 @@ public final class ShortsFilter extends Filter {
|
||||
// This has a secondary effect of hiding all new un-identified actions
|
||||
// under the assumption that the user wants all suggestions hidden.
|
||||
if (isEverySuggestedActionFilterEnabled()) {
|
||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (suggestedActionsGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
return suggestedActionsGroupList.check(protobufBufferArray).isFiltered();
|
||||
}
|
||||
|
||||
} else {
|
||||
// Feed/search identifier components.
|
||||
if (matchedGroup == shelfHeader) {
|
||||
// Because the header is used in watch history and possibly other places, check for the index,
|
||||
// which is 0 when the shelf header is used for Shorts.
|
||||
if (contentIndex != 0) return false;
|
||||
}
|
||||
|
||||
if (!shouldHideShortsFeedItems()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Super class handles logging.
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
// Feed/search identifier components.
|
||||
if (matchedGroup == shelfHeader) {
|
||||
// Because the header is used in watch history and possibly other places, check for the index,
|
||||
// which is 0 when the shelf header is used for Shorts.
|
||||
if (contentIndex != 0) return false;
|
||||
}
|
||||
|
||||
return shouldHideShortsFeedItems();
|
||||
}
|
||||
|
||||
private static boolean shouldHideShortsFeedItems() {
|
||||
@@ -392,37 +385,6 @@ public final class ShortsFilter extends Filter {
|
||||
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) {
|
||||
pivotBarRef = new WeakReference<>(view);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
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 android.preference.ListPreference;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
@@ -21,8 +17,6 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||
@SuppressWarnings("unused")
|
||||
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.
|
||||
* <p>
|
||||
@@ -47,19 +41,14 @@ public class CustomPlaybackSpeedPatch {
|
||||
*/
|
||||
private static long lastTimeOldPlaybackMenuInvoked;
|
||||
|
||||
/**
|
||||
* PreferenceList entries and values, of all available playback speeds.
|
||||
*/
|
||||
private static String[] preferenceListEntries, preferenceListEntryValues;
|
||||
|
||||
static {
|
||||
final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get();
|
||||
|
||||
if (holdSpeed > 0 && holdSpeed <= PLAYBACK_SPEED_MAXIMUM) {
|
||||
TAP_AND_HOLD_SPEED = holdSpeed;
|
||||
} else {
|
||||
showInvalidCustomSpeedToast();
|
||||
Settings.SPEED_TAP_AND_HOLD.resetToDefault();
|
||||
TAP_AND_HOLD_SPEED = Settings.SPEED_TAP_AND_HOLD.get();
|
||||
TAP_AND_HOLD_SPEED = Settings.SPEED_TAP_AND_HOLD.resetToDefault();
|
||||
}
|
||||
|
||||
loadCustomSpeeds();
|
||||
@@ -117,33 +106,6 @@ public class CustomPlaybackSpeedPatch {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.extension.youtube.patches.theme;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.clamp;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
@@ -226,6 +227,7 @@ public final class SeekbarColorPatch {
|
||||
}
|
||||
|
||||
private static String loadRawResourceAsString(int resourceId) {
|
||||
//noinspection CharsetObjectCanBeUsed
|
||||
try (InputStream inputStream = Utils.getContext().getResources().openRawResource(resourceId);
|
||||
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
|
||||
return scanner.next();
|
||||
@@ -281,6 +283,20 @@ public final class SeekbarColorPatch {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return SEEKBAR_CUSTOM_COLOR_ENABLED
|
||||
@@ -363,14 +379,4 @@ public final class SeekbarColorPatch {
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ import java.util.concurrent.*;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
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.ReturnYouTubeDislikeApi;
|
||||
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 boolean IS_SPOOFING_TO_OLD_SEPARATOR_COLOR
|
||||
= SpoofAppVersionPatch.isSpoofingToLessThan("18.10.00");
|
||||
|
||||
/**
|
||||
* Cached lookup of all video ids.
|
||||
*/
|
||||
@@ -118,7 +114,7 @@ public class ReturnYouTubeDislike {
|
||||
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;
|
||||
private static final ShapeDrawable leftSeparatorShape;
|
||||
@@ -133,7 +129,7 @@ public class ReturnYouTubeDislike {
|
||||
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3.7f, dp);
|
||||
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.setBounds(leftSeparatorBounds);
|
||||
@@ -184,17 +180,8 @@ public class ReturnYouTubeDislike {
|
||||
* 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.
|
||||
* 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() {
|
||||
if (IS_SPOOFING_TO_OLD_SEPARATOR_COLOR) {
|
||||
return ThemeHelper.isDarkTheme()
|
||||
? 0x29AAAAAA // transparent dark gray
|
||||
: 0xFFD9D9D9; // light gray
|
||||
}
|
||||
return ThemeHelper.isDarkTheme()
|
||||
? 0x33FFFFFF
|
||||
: 0xFFD9D9D9;
|
||||
@@ -248,10 +235,10 @@ public class ReturnYouTubeDislike {
|
||||
final boolean compactLayout = Settings.RYD_COMPACT_LAYOUT.get();
|
||||
|
||||
if (!compactLayout) {
|
||||
String leftSeparatorString = getTextDirectionString();
|
||||
String leftSeparatorString = Utils.getTextDirectionString();
|
||||
final Spannable leftSeparatorSpan;
|
||||
if (isRollingNumber) {
|
||||
leftSeparatorSpan = new SpannableString(leftSeparatorString);
|
||||
leftSeparatorSpan = new SpannableString(leftSeparatorString);
|
||||
} else {
|
||||
leftSeparatorString += " ";
|
||||
leftSeparatorSpan = new SpannableString(leftSeparatorString);
|
||||
@@ -292,12 +279,6 @@ public class ReturnYouTubeDislike {
|
||||
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.
|
||||
*/
|
||||
@@ -636,7 +617,7 @@ public class ReturnYouTubeDislike {
|
||||
userVote = vote;
|
||||
clearUICache();
|
||||
}
|
||||
|
||||
|
||||
if (future.isDone()) {
|
||||
// Update the fetched vote data.
|
||||
RYDVoteData voteData = getFetchData(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,6 @@ import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
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.spoof.SpoofAppVersionPatch;
|
||||
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.
|
||||
@@ -84,31 +80,19 @@ public class LicenseActivityHook {
|
||||
public static void initialize(Activity licenseActivity) {
|
||||
try {
|
||||
ThemeHelper.setActivityTheme(licenseActivity);
|
||||
ThemeHelper.setNavigationBarColor(licenseActivity.getWindow());
|
||||
licenseActivity.setContentView(getResourceIdentifier(
|
||||
"revanced_settings_with_toolbar", "layout"));
|
||||
|
||||
PreferenceFragment fragment;
|
||||
String toolbarTitleResourceName;
|
||||
String dataString = Objects.requireNonNull(licenseActivity.getIntent().getDataString());
|
||||
switch (dataString) {
|
||||
case "revanced_sb_settings_intent":
|
||||
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;
|
||||
// Sanity check.
|
||||
String dataString = licenseActivity.getIntent().getDataString();
|
||||
if (!"revanced_settings_intent".equals(dataString)) {
|
||||
Logger.printException(() -> "Unknown intent: " + dataString);
|
||||
return;
|
||||
}
|
||||
|
||||
createToolbar(licenseActivity, toolbarTitleResourceName);
|
||||
PreferenceFragment fragment = new ReVancedPreferenceFragment();
|
||||
createToolbar(licenseActivity, fragment);
|
||||
|
||||
//noinspection deprecation
|
||||
licenseActivity.getFragmentManager()
|
||||
@@ -121,20 +105,19 @@ public class LicenseActivityHook {
|
||||
}
|
||||
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
private static void createToolbar(Activity activity, String toolbarTitleResourceName) {
|
||||
private static void createToolbar(Activity activity, PreferenceFragment fragment) {
|
||||
// Replace dummy placeholder toolbar.
|
||||
// This is required to fix submenu title alignment issue with Android ASOP 15+
|
||||
ViewGroup toolBarParent = activity.findViewById(
|
||||
getResourceIdentifier("revanced_toolbar_parent", "id"));
|
||||
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent,"revanced_toolbar");
|
||||
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent, "revanced_toolbar");
|
||||
toolbarLayoutParams = dummyToolbar.getLayoutParams();
|
||||
toolBarParent.removeView(dummyToolbar);
|
||||
|
||||
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
|
||||
toolbar.setBackgroundColor(ThemeHelper.getToolbarBackgroundColor());
|
||||
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
|
||||
toolbar.setNavigationOnClickListener(view -> activity.onBackPressed());
|
||||
toolbar.setTitle(getResourceIdentifier(toolbarTitleResourceName, "string"));
|
||||
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
|
||||
|
||||
final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16,
|
||||
Utils.getContext().getResources().getDisplayMetrics());
|
||||
@@ -147,7 +130,11 @@ public class LicenseActivityHook {
|
||||
}
|
||||
setToolbarLayoutParams(toolbar);
|
||||
|
||||
// Add Search Icon and EditText for ReVancedPreferenceFragment only.
|
||||
if (fragment instanceof ReVancedPreferenceFragment) {
|
||||
SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
|
||||
}
|
||||
|
||||
toolBarParent.addView(toolbar, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,14 @@ package app.revanced.extension.youtube.settings;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.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.parent;
|
||||
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.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
||||
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.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.MiniplayerType;
|
||||
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.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
||||
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.MANUAL_SKIP;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||
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 app.revanced.extension.shared.Logger;
|
||||
@@ -38,11 +40,11 @@ import app.revanced.extension.shared.settings.IntegerSetting;
|
||||
import app.revanced.extension.shared.settings.LongSetting;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
|
||||
import app.revanced.extension.youtube.patches.MiniplayerPatch;
|
||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
||||
|
||||
public class Settings extends BaseSettings {
|
||||
@@ -103,8 +105,9 @@ public class Settings extends BaseSettings {
|
||||
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_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_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
|
||||
// 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_SUBSCRIPTIONS = new EnumSetting<>("revanced_alt_thumbnail_subscription", ThumbnailOption.ORIGINAL);
|
||||
@@ -126,6 +129,7 @@ public class Settings extends BaseSettings {
|
||||
// Player
|
||||
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
||||
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
||||
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
|
||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
||||
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
||||
@@ -138,6 +142,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_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_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_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
|
||||
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
|
||||
@@ -159,7 +164,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_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_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_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);
|
||||
@@ -171,7 +176,8 @@ public class Settings extends BaseSettings {
|
||||
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));
|
||||
// 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_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);
|
||||
@@ -179,6 +185,8 @@ 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_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
|
||||
// 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_ASK_SECTION = new BooleanSetting("revanced_hide_ask_section", FALSE);
|
||||
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
|
||||
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
|
||||
public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE);
|
||||
@@ -196,10 +204,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_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_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
|
||||
// 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_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_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);
|
||||
@@ -213,6 +222,8 @@ public class Settings extends BaseSettings {
|
||||
|
||||
// General layout
|
||||
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 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);
|
||||
@@ -221,7 +232,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 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 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
|
||||
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));
|
||||
@@ -234,7 +247,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 SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true,
|
||||
"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_DARK = new BooleanSetting("revanced_disable_translucent_navigation_bar_dark", FALSE, true);
|
||||
|
||||
@@ -293,7 +307,6 @@ public class Settings extends BaseSettings {
|
||||
// Misc
|
||||
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 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 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);
|
||||
@@ -318,12 +331,15 @@ public class Settings extends BaseSettings {
|
||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||
public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
|
||||
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));
|
||||
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));
|
||||
public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true,
|
||||
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,
|
||||
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));
|
||||
@@ -331,19 +347,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));
|
||||
|
||||
// ReturnYoutubeDislike
|
||||
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("ryd_enabled", TRUE);
|
||||
public static final StringSetting RYD_USER_ID = new StringSetting("ryd_user_id", "", false, false);
|
||||
public static final BooleanSetting RYD_SHORTS = new BooleanSetting("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_COMPACT_LAYOUT = new BooleanSetting("ryd_compact_layout", FALSE, parent(RYD_ENABLED));
|
||||
public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("ryd_estimated_like", 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_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);
|
||||
public static final StringSetting RYD_USER_ID = new StringSetting("revanced_ryd_user_id", "", false, false);
|
||||
public static final BooleanSetting RYD_SHORTS = new BooleanSetting("revanced_ryd_shorts", TRUE, 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("revanced_ryd_compact_layout", FALSE, 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("revanced_ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED));
|
||||
|
||||
// SponsorBlock
|
||||
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
|
||||
/**
|
||||
* Do not use directly, instead use {@link SponsorBlockSettings}
|
||||
*/
|
||||
/** Do not use id setting 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 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));
|
||||
@@ -365,54 +379,52 @@ public class Settings extends BaseSettings {
|
||||
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_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_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_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_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_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_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_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_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_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_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF");
|
||||
public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f);
|
||||
|
||||
// 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_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", FALSE);
|
||||
private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127);
|
||||
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
|
||||
private static final BooleanSetting DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE);
|
||||
private static final BooleanSetting DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
||||
private static final BooleanSetting DEPRECATED_AUTO_CAPTIONS = new BooleanSetting("revanced_auto_captions", FALSE);
|
||||
|
||||
static {
|
||||
// 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_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
|
||||
|
||||
migrateOldSettingToNew(DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN, HIDE_END_SCREEN_SUGGESTED_VIDEO);
|
||||
|
||||
migrateOldSettingToNew(DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU, ADVANCED_VIDEO_QUALITY_MENU);
|
||||
migrateOldSettingToNew(DEPRECATED_AUTO_CAPTIONS, DISABLE_AUTO_CAPTIONS);
|
||||
|
||||
// Migrate renamed enum.
|
||||
//noinspection deprecation
|
||||
@@ -449,6 +461,22 @@ public class Settings extends BaseSettings {
|
||||
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
|
||||
|
||||
// region SB import/export callbacks
|
||||
@@ -458,4 +486,3 @@ public class Settings extends BaseSettings {
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* Allows tapping the DeArrow about preference to open the DeArrow website.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class AlternativeThumbnailsAboutDeArrowPreference extends Preference {
|
||||
@SuppressWarnings("unused")
|
||||
public class AlternativeThumbnailsAboutDeArrowPreference extends UrlLinkPreference {
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse("https://dearrow.ajay.app"));
|
||||
pref.getContext().startActivity(i);
|
||||
return false;
|
||||
});
|
||||
externalUrl = "https://dearrow.ajay.app";
|
||||
}
|
||||
|
||||
public AlternativeThumbnailsAboutDeArrowPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -19,12 +19,15 @@ public class HtmlPreference extends Preference {
|
||||
public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public HtmlPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public HtmlPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package app.revanced.extension.youtube.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
@@ -9,35 +10,66 @@ import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceGroup;
|
||||
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.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
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.Utils;
|
||||
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.NoTitlePreferenceCategory;
|
||||
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.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
||||
|
||||
/**
|
||||
* Preference fragment for ReVanced settings.
|
||||
*
|
||||
* @noinspection deprecation
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
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")
|
||||
public static Drawable getBackButtonDrawable() {
|
||||
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
|
||||
@@ -48,85 +80,140 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts a preference list by menu entries, but preserves the first value as the first entry.
|
||||
*
|
||||
* @noinspection SameParameterValue
|
||||
* Initializes the preference fragment, copying the original screen to allow full restoration.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
pairsToSort.sort((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
|
||||
protected void initialize() {
|
||||
super.initialize();
|
||||
|
||||
try {
|
||||
setPreferenceScreenToolbar(getPreferenceScreen());
|
||||
preferenceScreen = getPreferenceScreen();
|
||||
Utils.sortPreferenceGroups(preferenceScreen);
|
||||
|
||||
// If the preference was included, then initialize it based on the available playback speed.
|
||||
Preference preference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
|
||||
if (preference instanceof ListPreference playbackPreference) {
|
||||
CustomPlaybackSpeedPatch.initializeListPreference(playbackPreference);
|
||||
// Store the original structure for restoration after filtering.
|
||||
originalPreferenceScreen = getPreferenceManager().createPreferenceScreen(getContext());
|
||||
for (int i = 0, count = preferenceScreen.getPreferenceCount(); i < count; i++) {
|
||||
originalPreferenceScreen.addPreference(preferenceScreen.getPreference(i));
|
||||
}
|
||||
|
||||
sortPreferenceListMenu(Settings.CHANGE_START_PAGE);
|
||||
sortPreferenceListMenu(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE);
|
||||
sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE);
|
||||
setPreferenceScreenToolbar(preferenceScreen);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "initialize failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void sortPreferenceListMenu(EnumSetting<?> setting) {
|
||||
Preference preference = findPreference(setting.key);
|
||||
if (preference instanceof ListPreference languagePreference) {
|
||||
sortListPreferenceByValues(languagePreference, 1);
|
||||
/**
|
||||
* Called when the fragment starts, ensuring all preferences are collected after initialization.
|
||||
*/
|
||||
@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) {
|
||||
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);
|
||||
if (childPreference instanceof PreferenceScreen) {
|
||||
// Recursively set sub preferences.
|
||||
@@ -139,11 +226,16 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
.findViewById(android.R.id.content)
|
||||
.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) {
|
||||
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
@@ -152,6 +244,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
toolbar.setTitle(childScreen.getTitle());
|
||||
toolbar.setNavigationIcon(getBackButtonDrawable());
|
||||
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
|
||||
|
||||
final int margin = (int) TypedValue.applyDimension(
|
||||
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
|
||||
);
|
||||
@@ -173,3 +266,277 @@ 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() {
|
||||
if (highlightingApplied) {
|
||||
preference.setSummary(originalSummary);
|
||||
}
|
||||
|
||||
super.clearHighlighting();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (highlightingApplied) {
|
||||
preference.setSummaryOn(originalSummaryOn);
|
||||
preference.setSummaryOff(originalSummaryOff);
|
||||
}
|
||||
|
||||
super.clearHighlighting();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
if (highlightingApplied) {
|
||||
preference.setEntries(originalEntries);
|
||||
}
|
||||
|
||||
super.clearHighlighting();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -73,6 +73,7 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
||||
if (currentClientType == clientType) {
|
||||
return;
|
||||
}
|
||||
currentClientType = clientType;
|
||||
|
||||
Logger.printDebug(() -> "Updating spoof stream side effects preference");
|
||||
setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ 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.ui.SponsorBlockPreferenceGroup;
|
||||
import app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour;
|
||||
import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
|
||||
|
||||
@@ -31,6 +32,7 @@ public class SponsorBlockSettings {
|
||||
@Override
|
||||
public void settingsImported(@Nullable Context context) {
|
||||
SegmentCategory.loadAllCategoriesFromSettings();
|
||||
SponsorBlockPreferenceGroup.settingsImported = true;
|
||||
}
|
||||
@Override
|
||||
public void settingsExported(@Nullable Context context) {
|
||||
@@ -136,7 +138,7 @@ public class SponsorBlockSettings {
|
||||
for (SegmentCategory category : categories) {
|
||||
JSONObject categoryObject = new JSONObject();
|
||||
String categoryKey = category.keyValue;
|
||||
categoryObject.put("color", category.colorString());
|
||||
categoryObject.put("color", category.getColorString());
|
||||
barTypesObject.put(categoryKey, categoryObject);
|
||||
|
||||
if (category.behaviour != CategoryBehaviour.IGNORE) {
|
||||
|
||||
@@ -5,7 +5,12 @@ import static app.revanced.extension.shared.StringRef.str;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
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 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.
|
||||
*/
|
||||
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 Pattern manualEditTimePattern
|
||||
= 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.voteTypesWithoutCategoryChange // highlight segments cannot change category
|
||||
: 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];
|
||||
String title = voteOption.title.toString();
|
||||
if (Settings.SB_USER_IS_VIP.get() && segment.isLocked && voteOption.shouldHighlight) {
|
||||
items[i] = Html.fromHtml(String.format("<font color=\"%s\">%s</font>", LOCKED_COLOR, title));
|
||||
} else {
|
||||
items[i] = title;
|
||||
CharSequence title = voteOption.title.toString();
|
||||
if (userIsVip && segment.isLocked && voteOption.highlightIfVipAndVideoIsLocked) {
|
||||
SpannableString coloredTitle = new SpannableString(title);
|
||||
coloredTitle.setSpan(new ForegroundColorSpan(LOCKED_COLOR),
|
||||
0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
title = coloredTitle;
|
||||
}
|
||||
items[i] = title;
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setItems(items, (dialog1, which1) -> {
|
||||
SegmentVote voteOption = voteOptions[which1];
|
||||
switch (voteOption) {
|
||||
case UPVOTE:
|
||||
case DOWNVOTE:
|
||||
SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption);
|
||||
break;
|
||||
case CATEGORY_CHANGE:
|
||||
onNewCategorySelect(segment, context);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.show();
|
||||
new AlertDialog.Builder(context).setItems(items, (dialog1, which1) -> {
|
||||
SegmentVote voteOption = voteOptions[which1];
|
||||
switch (voteOption) {
|
||||
case UPVOTE:
|
||||
case DOWNVOTE:
|
||||
SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption);
|
||||
break;
|
||||
case CATEGORY_CHANGE:
|
||||
onNewCategorySelect(segment, context);
|
||||
break;
|
||||
}
|
||||
}).show();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "segmentVoteClickListener failure", ex);
|
||||
}
|
||||
@@ -282,7 +289,6 @@ public class SponsorBlockUtils {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final int numberOfSegments = segments.length;
|
||||
CharSequence[] titles = new CharSequence[numberOfSegments];
|
||||
for (int i = 0; i < numberOfSegments; i++) {
|
||||
@@ -290,22 +296,33 @@ public class SponsorBlockUtils {
|
||||
if (segment.category == SegmentCategory.UNSUBMITTED) {
|
||||
continue;
|
||||
}
|
||||
StringBuilder htmlBuilder = new StringBuilder();
|
||||
htmlBuilder.append(String.format("<b><font color=\"#%06X\">⬤</font> %s<br>",
|
||||
segment.category.color, segment.category.title));
|
||||
htmlBuilder.append(formatSegmentTime(segment.start));
|
||||
if (segment.category != SegmentCategory.HIGHLIGHT) {
|
||||
htmlBuilder.append(" to ").append(formatSegmentTime(segment.end));
|
||||
|
||||
SpannableStringBuilder spannableBuilder = new SpannableStringBuilder();
|
||||
|
||||
spannableBuilder.append(segment.category.getTitleWithColorDot());
|
||||
spannableBuilder.append('\n');
|
||||
|
||||
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
|
||||
htmlBuilder.append("<br>");
|
||||
titles[i] = Html.fromHtml(htmlBuilder.toString());
|
||||
|
||||
if (i + 1 != numberOfSegments) {
|
||||
// prevents trailing new line after last segment
|
||||
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)
|
||||
.setItems(titles, segmentVoteClickListener)
|
||||
.show();
|
||||
new AlertDialog.Builder(context).setItems(titles, segmentVoteClickListener).show();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onVotingClicked failure", ex);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user