mirror of
https://github.com/ReVanced/revanced-patches.git
synced 2026-01-23 18:51:03 +00:00
Compare commits
221 Commits
v5.26.0-de
...
v5.32.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5fc187a35 | ||
|
|
f46dbcd084 | ||
|
|
2136573cb6 | ||
|
|
86ec08993c | ||
|
|
44da5a71c5 | ||
|
|
e4e81b89ea | ||
|
|
165df659a1 | ||
|
|
bb87afe0f6 | ||
|
|
ac5fb17937 | ||
|
|
e88356b3c5 | ||
|
|
dead9c2d94 | ||
|
|
ca640b2839 | ||
|
|
c972267cd8 | ||
|
|
d0d2c13d16 | ||
|
|
e7b4ab53cf | ||
|
|
f994264d9c | ||
|
|
eb61c1f5d1 | ||
|
|
e578347277 | ||
|
|
294b2dce2e | ||
|
|
aa37105ea3 | ||
|
|
eb57a2697b | ||
|
|
19bc5b63c5 | ||
|
|
2b93ff6cfc | ||
|
|
cc6984e919 | ||
|
|
8bf575e778 | ||
|
|
2e625ee1a2 | ||
|
|
6bcba48ee7 | ||
|
|
c3034edc43 | ||
|
|
82255a09d3 | ||
|
|
594dce13cd | ||
|
|
479e205808 | ||
|
|
3d1b7e8101 | ||
|
|
e951184b7a | ||
|
|
d088b1e7ed | ||
|
|
a38f635514 | ||
|
|
b3e6c215cc | ||
|
|
c9cc3d5c41 | ||
|
|
536e64565c | ||
|
|
65cbf3c1eb | ||
|
|
61c1a7a75a | ||
|
|
1e39db06b8 | ||
|
|
e019f83232 | ||
|
|
3b57a5f8c0 | ||
|
|
eafe3dfc45 | ||
|
|
d56d8d990c | ||
|
|
37a8682901 | ||
|
|
11ba7d4e3e | ||
|
|
6833d37c26 | ||
|
|
e6f72bcb7d | ||
|
|
e8a227c082 | ||
|
|
0472ec2830 | ||
|
|
6412a5cb1a | ||
|
|
cc548689ac | ||
|
|
a3d47e72e3 | ||
|
|
f37482443a | ||
|
|
cc4aef89d3 | ||
|
|
1c0a0eb4b5 | ||
|
|
b1d6c46763 | ||
|
|
42195b9f63 | ||
|
|
a4e08ea13d | ||
|
|
bd2a939a72 | ||
|
|
a89179ab79 | ||
|
|
b0129d383a | ||
|
|
23b6c42630 | ||
|
|
10f4464735 | ||
|
|
4e5addbba5 | ||
|
|
8d11ede927 | ||
|
|
83a3f4da00 | ||
|
|
caf3b69731 | ||
|
|
3135203b55 | ||
|
|
8d113a7c67 | ||
|
|
4e742075f3 | ||
|
|
04caa66662 | ||
|
|
dacc85f5e7 | ||
|
|
f9abec358a | ||
|
|
7e11514cc1 | ||
|
|
2e9c8df8f6 | ||
|
|
4c8cfc8800 | ||
|
|
0ba6fad33f | ||
|
|
3eac215e13 | ||
|
|
90a3262f68 | ||
|
|
f7f49b834e | ||
|
|
89ec5d5bc6 | ||
|
|
e3bc8be936 | ||
|
|
6c5c3f5a4d | ||
|
|
629bd0644b | ||
|
|
b4005079e3 | ||
|
|
a354c443ad | ||
|
|
d1313e3ea1 | ||
|
|
11338008c6 | ||
|
|
8b9e04475d | ||
|
|
d3c9dc6ed7 | ||
|
|
d7ed32571f | ||
|
|
d3935f03c0 | ||
|
|
b2e601f0f0 | ||
|
|
d3ec219a29 | ||
|
|
5ed07d4aaa | ||
|
|
209a3a3626 | ||
|
|
2b3419571f | ||
|
|
bbe504e616 | ||
|
|
6c32591f62 | ||
|
|
ad6da67281 | ||
|
|
14dc593eba | ||
|
|
e52ee41222 | ||
|
|
6ee94f8532 | ||
|
|
21688201af | ||
|
|
f08474369b | ||
|
|
ed617094ea | ||
|
|
9131c50f1b | ||
|
|
69600d08a4 | ||
|
|
5dba77612b | ||
|
|
92b588c866 | ||
|
|
da20e565cd | ||
|
|
ca694c78d2 | ||
|
|
e169056b70 | ||
|
|
b6bf1e026c | ||
|
|
9fa89d48c0 | ||
|
|
5d2c21540c | ||
|
|
1a8aacdff6 | ||
|
|
1804bd9bfc | ||
|
|
7eb4e62762 | ||
|
|
b8e10b5c1f | ||
|
|
a7c11b9b08 | ||
|
|
443c0a74d5 | ||
|
|
84a0f7f7d7 | ||
|
|
558bf8bca8 | ||
|
|
e22d4e6a4b | ||
|
|
a07f946633 | ||
|
|
29c86ac6a3 | ||
|
|
19cf5667d8 | ||
|
|
fb83e58f79 | ||
|
|
9844081d04 | ||
|
|
439ca37e99 | ||
|
|
113a3d9f19 | ||
|
|
978c24458b | ||
|
|
957bece3e9 | ||
|
|
d32c3ac51d | ||
|
|
26102a70a2 | ||
|
|
2b44bf4c23 | ||
|
|
0e63f49e13 | ||
|
|
674a5b8d29 | ||
|
|
7be374100b | ||
|
|
e48c152b95 | ||
|
|
a678f178e1 | ||
|
|
2d8f5641f9 | ||
|
|
0dbd058099 | ||
|
|
c1a8fd0766 | ||
|
|
d338989cb4 | ||
|
|
b94daacf01 | ||
|
|
c764c4f197 | ||
|
|
6b719dfcd7 | ||
|
|
ccd169121a | ||
|
|
dcfbd8bf93 | ||
|
|
b65697603d | ||
|
|
25da5cca8b | ||
|
|
2b62fc2224 | ||
|
|
a9e9456b6b | ||
|
|
b01523e97d | ||
|
|
b8afb4e821 | ||
|
|
0d2198faed | ||
|
|
5c7c407b82 | ||
|
|
a8d2a1e028 | ||
|
|
d31624cae8 | ||
|
|
e790cfbf59 | ||
|
|
a54d408d3e | ||
|
|
5d3769e921 | ||
|
|
e138501657 | ||
|
|
a9235d6b62 | ||
|
|
90868ff025 | ||
|
|
345ec5c430 | ||
|
|
c8b95d475c | ||
|
|
0a93f44a5e | ||
|
|
a426e2af50 | ||
|
|
9e30c34e74 | ||
|
|
55401368b8 | ||
|
|
c0c56fef23 | ||
|
|
69df47602f | ||
|
|
1cea6bfdff | ||
|
|
e2a9552f91 | ||
|
|
7bbaca77ad | ||
|
|
246f3efe55 | ||
|
|
0fca3e8fb1 | ||
|
|
c09255eaed | ||
|
|
e78d6240ea | ||
|
|
ed0d807d70 | ||
|
|
1c39004350 | ||
|
|
6127f48a9e | ||
|
|
ad416f4aa7 | ||
|
|
adfac8a1f2 | ||
|
|
498488d45b | ||
|
|
f8e31c820a | ||
|
|
826a391591 | ||
|
|
af827e2f1a | ||
|
|
97cd31509e | ||
|
|
c0448dece4 | ||
|
|
f00a95c0d8 | ||
|
|
7a432e5741 | ||
|
|
966a78bd81 | ||
|
|
6aff8e8ca4 | ||
|
|
11aa463fa6 | ||
|
|
bf1b639a2f | ||
|
|
6d5380d44d | ||
|
|
7e1547b5b9 | ||
|
|
c790b45cc5 | ||
|
|
65fc6b43f5 | ||
|
|
2257dd90aa | ||
|
|
4b8499ff2c | ||
|
|
bde3fda972 | ||
|
|
e2e07b5cb2 | ||
|
|
9d10ab6c00 | ||
|
|
d7644152fd | ||
|
|
9be21f4824 | ||
|
|
a2eae0bf04 | ||
|
|
679354b5b3 | ||
|
|
91dec21033 | ||
|
|
1d0c56819b | ||
|
|
4410816c22 | ||
|
|
7e4e48bc9f | ||
|
|
e435b33593 | ||
|
|
bf288b83ae | ||
|
|
7a53580380 |
2
.github/workflows/build_pull_request.yml
vendored
2
.github/workflows/build_pull_request.yml
vendored
@@ -13,8 +13,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
|
|||||||
1
.github/workflows/pull_strings.yml
vendored
1
.github/workflows/pull_strings.yml
vendored
@@ -17,7 +17,6 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: dev
|
ref: dev
|
||||||
fetch-depth: 0
|
|
||||||
clean: true
|
clean: true
|
||||||
|
|
||||||
- name: Pull strings
|
- name: Pull strings
|
||||||
|
|||||||
2
.github/workflows/push_strings.yml
vendored
2
.github/workflows/push_strings.yml
vendored
@@ -15,8 +15,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Preprocess strings
|
- name: Preprocess strings
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -19,8 +19,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
|
|||||||
685
CHANGELOG.md
685
CHANGELOG.md
@@ -1,3 +1,688 @@
|
|||||||
|
# [5.32.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.3-dev.1...v5.32.0-dev.1) (2025-07-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Prime Video:** Add `Playback speed` patch ([#5444](https://github.com/ReVanced/revanced-patches/issues/5444)) ([22cf313](https://github.com/ReVanced/revanced-patches/commit/22cf313a7b99b69e17b9d488c514802043a5dc10))
|
||||||
|
|
||||||
|
## [5.31.3-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.2...v5.31.3-dev.1) (2025-07-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - GmsCore support:** Fix search suggestions when logged out by using correct search provider ([#5483](https://github.com/ReVanced/revanced-patches/issues/5483)) ([e86fdc8](https://github.com/ReVanced/revanced-patches/commit/e86fdc86b161a6077960b85149e83bacbac664e7))
|
||||||
|
|
||||||
|
## [5.31.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.1...v5.31.2) (2025-07-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Spoof client:** Fix login failing by spoofing login request in addition ([#5448](https://github.com/ReVanced/revanced-patches/issues/5448)) ([4e59ddc](https://github.com/ReVanced/revanced-patches/commit/4e59ddc62388d09f71b89593fc8b76933d9facea))
|
||||||
|
* **YouTube - Disable double tap actions:** Remove old incompatible targets ([857053e](https://github.com/ReVanced/revanced-patches/commit/857053e29b72ded10a84b0ac693fa107705342d9))
|
||||||
|
* **YouTube - Hide layout components:** Hide quick actions does not work ([#5423](https://github.com/ReVanced/revanced-patches/issues/5423)) ([9c66729](https://github.com/ReVanced/revanced-patches/commit/9c6672946d44001e106bdac9041e2d79ef3f6ab2))
|
||||||
|
* **YouTube - Hide layout components:** Show correct custom header logo if 'Hide YouTube Doodles' is enabled ([#5431](https://github.com/ReVanced/revanced-patches/issues/5431)) ([20cc141](https://github.com/ReVanced/revanced-patches/commit/20cc141e61f75de1a1749247c4f4aed167dee8ea))
|
||||||
|
* **YouTube - Settings:** Back button/gesture closes search instead of exiting ([#5418](https://github.com/ReVanced/revanced-patches/issues/5418)) ([134b278](https://github.com/ReVanced/revanced-patches/commit/134b278baa7b90d2c4b06200cabacabf55ebc055))
|
||||||
|
|
||||||
|
## [5.31.2-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.4...v5.31.2-dev.5) (2025-07-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Spoof client:** Fix login failing by spoofing login request in addition ([#5448](https://github.com/ReVanced/revanced-patches/issues/5448)) ([4e59ddc](https://github.com/ReVanced/revanced-patches/commit/4e59ddc62388d09f71b89593fc8b76933d9facea))
|
||||||
|
|
||||||
|
## [5.31.2-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.3...v5.31.2-dev.4) (2025-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Settings:** Back button/gesture closes search instead of exiting ([#5418](https://github.com/ReVanced/revanced-patches/issues/5418)) ([134b278](https://github.com/ReVanced/revanced-patches/commit/134b278baa7b90d2c4b06200cabacabf55ebc055))
|
||||||
|
|
||||||
|
## [5.31.2-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.2...v5.31.2-dev.3) (2025-07-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Disable double tap actions:** Remove old incompatible targets ([857053e](https://github.com/ReVanced/revanced-patches/commit/857053e29b72ded10a84b0ac693fa107705342d9))
|
||||||
|
|
||||||
|
## [5.31.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.2-dev.1...v5.31.2-dev.2) (2025-07-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Show correct custom header logo if 'Hide YouTube Doodles' is enabled ([#5431](https://github.com/ReVanced/revanced-patches/issues/5431)) ([20cc141](https://github.com/ReVanced/revanced-patches/commit/20cc141e61f75de1a1749247c4f4aed167dee8ea))
|
||||||
|
|
||||||
|
## [5.31.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.1...v5.31.2-dev.1) (2025-07-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide quick actions does not work ([#5423](https://github.com/ReVanced/revanced-patches/issues/5423)) ([9c66729](https://github.com/ReVanced/revanced-patches/commit/9c6672946d44001e106bdac9041e2d79ef3f6ab2))
|
||||||
|
|
||||||
|
## [5.31.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.0...v5.31.1) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Unlock Premium:** Fix hiding context menu ads for latest version ([#5415](https://github.com/ReVanced/revanced-patches/issues/5415)) ([dcde393](https://github.com/ReVanced/revanced-patches/commit/dcde3935bde3172576d0f9f5ff9eb62ecfff7dfe))
|
||||||
|
|
||||||
|
## [5.31.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.31.0...v5.31.1-dev.1) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Unlock Premium:** Fix hiding context menu ads for latest version ([#5415](https://github.com/ReVanced/revanced-patches/issues/5415)) ([dcde393](https://github.com/ReVanced/revanced-patches/commit/dcde3935bde3172576d0f9f5ff9eb62ecfff7dfe))
|
||||||
|
|
||||||
|
# [5.31.0](https://github.com/ReVanced/revanced-patches/compare/v5.30.0...v5.31.0) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([72459bb](https://github.com/ReVanced/revanced-patches/commit/72459bb2eaf4691e32822dfdd1db3240e2fe98dd))
|
||||||
|
* Correctly name `Enable ROM signature spoofing` patch ([d85881a](https://github.com/ReVanced/revanced-patches/commit/d85881a6768232a999534677bebb248e640fe5ab))
|
||||||
|
* Fix accidental changes ([e2ac841](https://github.com/ReVanced/revanced-patches/commit/e2ac8419756e3c7d62e2c0430a2918a3c1c63666))
|
||||||
|
* Fix refactoring typo ([ec0ae42](https://github.com/ReVanced/revanced-patches/commit/ec0ae42496628cdeb2a639020fce94316b41b751))
|
||||||
|
* Handle empty list of announcements ([de9d720](https://github.com/ReVanced/revanced-patches/commit/de9d7209f4e818a618a7fd9000013ae8ebd728f2))
|
||||||
|
* **SoundCloud:** Constrain patches to last working app target ([e8ea89f](https://github.com/ReVanced/revanced-patches/commit/e8ea89fc1a3f0531a0af7529663f13328aca4fe7))
|
||||||
|
* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([8633544](https://github.com/ReVanced/revanced-patches/commit/8633544decc0814d7a548fbc5576b4bdd1d7eee0))
|
||||||
|
* **Spotify:** Remove other ads type from the browse screen ([#5333](https://github.com/ReVanced/revanced-patches/issues/5333)) ([c68533a](https://github.com/ReVanced/revanced-patches/commit/c68533a33a399ca813380b5c9ccddce434ceadf8))
|
||||||
|
* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([47e6b62](https://github.com/ReVanced/revanced-patches/commit/47e6b62f3d8b07960cfb2963f441222d3e67df92))
|
||||||
|
* **YouTube - Hide ads:** Hide new type of general ad ([#5345](https://github.com/ReVanced/revanced-patches/issues/5345)) ([f23716b](https://github.com/ReVanced/revanced-patches/commit/f23716bc52c03d8d0271bfe38b19247e6de7021d))
|
||||||
|
* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([5f3e48e](https://github.com/ReVanced/revanced-patches/commit/5f3e48ec5853f6439800ef58239291c34bcab5f6))
|
||||||
|
* **YouTube - Playback speed:** Allow custom speeds with 0.01x precision ([#5360](https://github.com/ReVanced/revanced-patches/issues/5360)) ([0eecef0](https://github.com/ReVanced/revanced-patches/commit/0eecef00fc93d2a217944978e29dce82e3134e35))
|
||||||
|
* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([dbc9c5f](https://github.com/ReVanced/revanced-patches/commit/dbc9c5f00c1f5bbb95f8822667cc1ac3c613fa00))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a42c98f](https://github.com/ReVanced/revanced-patches/commit/a42c98f8b51fd37d815fd38b75a2b7ccc4fb049b))
|
||||||
|
* **Lightroom:** Constrain patches to last working version ([#5335](https://github.com/ReVanced/revanced-patches/issues/5335)) ([32ce70e](https://github.com/ReVanced/revanced-patches/commit/32ce70e994f354b9a569376bb89eb38b3190e6f9))
|
||||||
|
* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([e36d4c1](https://github.com/ReVanced/revanced-patches/commit/e36d4c1986b58815c7659e6ef44011166873f9c8))
|
||||||
|
* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([9d31238](https://github.com/ReVanced/revanced-patches/commit/9d31238803a45e957472760fc40c3862da2cf3f0))
|
||||||
|
* **YouTube - Change header:** Add in-app setting to change the app header ([#5346](https://github.com/ReVanced/revanced-patches/issues/5346)) ([9ba45b6](https://github.com/ReVanced/revanced-patches/commit/9ba45b6680595d732b47e8fa54bee98b7c7af179))
|
||||||
|
* **YouTube - Hide layout components:** Add `Hide channel links preview` and `Hide 'Visit Community' button` in channel page ([#5320](https://github.com/ReVanced/revanced-patches/issues/5320)) ([9d9cce3](https://github.com/ReVanced/revanced-patches/commit/9d9cce3ec5550b2fea88df745f1700bb2f17eb9e))
|
||||||
|
* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([71db0a2](https://github.com/ReVanced/revanced-patches/commit/71db0a2661b5f76eb5048cdeed83f26fbfdf4fee))
|
||||||
|
|
||||||
|
# [5.31.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.16...v5.31.0-dev.17) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Unlock Premium:** Remove wrongfully hidden non ad browse sections ([#5403](https://github.com/ReVanced/revanced-patches/issues/5403)) ([8633544](https://github.com/ReVanced/revanced-patches/commit/8633544decc0814d7a548fbc5576b4bdd1d7eee0))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify:** Remove support for old versions ([#5404](https://github.com/ReVanced/revanced-patches/issues/5404)) ([9d31238](https://github.com/ReVanced/revanced-patches/commit/9d31238803a45e957472760fc40c3862da2cf3f0))
|
||||||
|
|
||||||
|
# [5.31.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.15...v5.31.0-dev.16) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify - Spoof client:** Fix issues like songs skipping by spoofing to iOS ([#5388](https://github.com/ReVanced/revanced-patches/issues/5388)) ([e36d4c1](https://github.com/ReVanced/revanced-patches/commit/e36d4c1986b58815c7659e6ef44011166873f9c8))
|
||||||
|
* **YouTube:** Disable two-finger tap gesture for skipping chapters ([#5374](https://github.com/ReVanced/revanced-patches/issues/5374)) ([71db0a2](https://github.com/ReVanced/revanced-patches/commit/71db0a2661b5f76eb5048cdeed83f26fbfdf4fee))
|
||||||
|
|
||||||
|
# [5.31.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.14...v5.31.0-dev.15) (2025-07-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Handle empty list of announcements ([de9d720](https://github.com/ReVanced/revanced-patches/commit/de9d7209f4e818a618a7fd9000013ae8ebd728f2))
|
||||||
|
|
||||||
|
# [5.31.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.13...v5.31.0-dev.14) (2025-07-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Bacon Reader - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5402](https://github.com/ReVanced/revanced-patches/issues/5402)) ([72459bb](https://github.com/ReVanced/revanced-patches/commit/72459bb2eaf4691e32822dfdd1db3240e2fe98dd))
|
||||||
|
|
||||||
|
# [5.31.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.12...v5.31.0-dev.13) (2025-07-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Slide to seek:** Show tap and hold 2x speed overlay when active ([#5398](https://github.com/ReVanced/revanced-patches/issues/5398)) ([dbc9c5f](https://github.com/ReVanced/revanced-patches/commit/dbc9c5f00c1f5bbb95f8822667cc1ac3c613fa00))
|
||||||
|
|
||||||
|
# [5.31.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.11...v5.31.0-dev.12) (2025-07-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Sync for Reddit - Spoof client:** Use www instead of ssl API to fix auth related issues ([#5392](https://github.com/ReVanced/revanced-patches/issues/5392)) ([47e6b62](https://github.com/ReVanced/revanced-patches/commit/47e6b62f3d8b07960cfb2963f441222d3e67df92))
|
||||||
|
|
||||||
|
# [5.31.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.10...v5.31.0-dev.11) (2025-07-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Cricbuzz - Hide ads:** Hide Cricbuzz11 UI elements ([#5381](https://github.com/ReVanced/revanced-patches/issues/5381)) ([a42c98f](https://github.com/ReVanced/revanced-patches/commit/a42c98f8b51fd37d815fd38b75a2b7ccc4fb049b))
|
||||||
|
|
||||||
|
# [5.31.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.9...v5.31.0-dev.10) (2025-07-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Do not hide playlist sort button if 'Hide AI comments summary' is on ([5f3e48e](https://github.com/ReVanced/revanced-patches/commit/5f3e48ec5853f6439800ef58239291c34bcab5f6))
|
||||||
|
|
||||||
|
# [5.31.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.8...v5.31.0-dev.9) (2025-07-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix accidental changes ([e2ac841](https://github.com/ReVanced/revanced-patches/commit/e2ac8419756e3c7d62e2c0430a2918a3c1c63666))
|
||||||
|
|
||||||
|
# [5.31.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.7...v5.31.0-dev.8) (2025-07-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Correctly name `Enable ROM signature spoofing` patch ([d85881a](https://github.com/ReVanced/revanced-patches/commit/d85881a6768232a999534677bebb248e640fe5ab))
|
||||||
|
|
||||||
|
# [5.31.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.6...v5.31.0-dev.7) (2025-07-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix refactoring typo ([ec0ae42](https://github.com/ReVanced/revanced-patches/commit/ec0ae42496628cdeb2a639020fce94316b41b751))
|
||||||
|
|
||||||
|
# [5.31.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.5...v5.31.0-dev.6) (2025-07-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Playback speed:** Allow custom speeds with 0.01x precision ([#5360](https://github.com/ReVanced/revanced-patches/issues/5360)) ([0eecef0](https://github.com/ReVanced/revanced-patches/commit/0eecef00fc93d2a217944978e29dce82e3134e35))
|
||||||
|
|
||||||
|
# [5.31.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.4...v5.31.0-dev.5) (2025-07-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Change header:** Add in-app setting to change the app header ([#5346](https://github.com/ReVanced/revanced-patches/issues/5346)) ([9ba45b6](https://github.com/ReVanced/revanced-patches/commit/9ba45b6680595d732b47e8fa54bee98b7c7af179))
|
||||||
|
|
||||||
|
# [5.31.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.3...v5.31.0-dev.4) (2025-07-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Hide new type of general ad ([#5345](https://github.com/ReVanced/revanced-patches/issues/5345)) ([f23716b](https://github.com/ReVanced/revanced-patches/commit/f23716bc52c03d8d0271bfe38b19247e6de7021d))
|
||||||
|
|
||||||
|
# [5.31.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.2...v5.31.0-dev.3) (2025-07-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify:** Remove other ads type from the browse screen ([#5333](https://github.com/ReVanced/revanced-patches/issues/5333)) ([c68533a](https://github.com/ReVanced/revanced-patches/commit/c68533a33a399ca813380b5c9ccddce434ceadf8))
|
||||||
|
|
||||||
|
# [5.31.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.31.0-dev.1...v5.31.0-dev.2) (2025-07-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Add `Hide channel links preview` and `Hide 'Visit Community' button` in channel page ([#5320](https://github.com/ReVanced/revanced-patches/issues/5320)) ([9d9cce3](https://github.com/ReVanced/revanced-patches/commit/9d9cce3ec5550b2fea88df745f1700bb2f17eb9e))
|
||||||
|
|
||||||
|
# [5.31.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.30.0...v5.31.0-dev.1) (2025-07-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **SoundCloud:** Constrain patches to last working app target ([e8ea89f](https://github.com/ReVanced/revanced-patches/commit/e8ea89fc1a3f0531a0af7529663f13328aca4fe7))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Lightroom:** Constrain patches to last working version ([#5335](https://github.com/ReVanced/revanced-patches/issues/5335)) ([32ce70e](https://github.com/ReVanced/revanced-patches/commit/32ce70e994f354b9a569376bb89eb38b3190e6f9))
|
||||||
|
|
||||||
|
# [5.30.0](https://github.com/ReVanced/revanced-patches/compare/v5.29.0...v5.30.0) (2025-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Spoof client patch:** Block sending bad integrity verdicts to potentially fix account suspensions ([#5274](https://github.com/ReVanced/revanced-patches/issues/5274)) ([f7b574c](https://github.com/ReVanced/revanced-patches/commit/f7b574ca79c5a616cfe33a3fc75bd8cf68571f7d))
|
||||||
|
* **Spotify - Spoof client:** Handle remaining edge cases to obtain a session ([#5285](https://github.com/ReVanced/revanced-patches/issues/5285)) ([2bb2d59](https://github.com/ReVanced/revanced-patches/commit/2bb2d594936093774e232ad8b274c81e805c5bf6))
|
||||||
|
* **Spotify - Spoof client:** Skip native login screens ([#5228](https://github.com/ReVanced/revanced-patches/issues/5228)) ([c5ebc63](https://github.com/ReVanced/revanced-patches/commit/c5ebc6336ed17cc9cc7f1348282a2aa3c173fb95))
|
||||||
|
* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([73fd832](https://github.com/ReVanced/revanced-patches/commit/73fd83222e089a5fd6e1526e5c12f5a1e9893a35))
|
||||||
|
* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions by simplifying fingerprint ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([dad0ff4](https://github.com/ReVanced/revanced-patches/commit/dad0ff4fba74c2b020fbde6c6d5eb66e10e6f1f7))
|
||||||
|
* **Spotify:** Add `Spoof client` patch to fix various issues by using a web platform access token ([#5173](https://github.com/ReVanced/revanced-patches/issues/5173)) ([b7b75bb](https://github.com/ReVanced/revanced-patches/commit/b7b75bb9d8d5fd505121e752b8a20e61ff28d1b2))
|
||||||
|
* **YouTube - Hide ads:** Fix "Hide shopping links" ([#5267](https://github.com/ReVanced/revanced-patches/issues/5267)) ([2fe4607](https://github.com/ReVanced/revanced-patches/commit/2fe46079d78ab98076d3a4cdf01c8bfdbdea45c0))
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide AI Comments summary" in Comments ([#5284](https://github.com/ReVanced/revanced-patches/issues/5284)) ([d42370e](https://github.com/ReVanced/revanced-patches/commit/d42370ef71f4608abc64b6ef4a3fb0c5bd5e3eb6))
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide AI-generated video summary" in video description ([#5269](https://github.com/ReVanced/revanced-patches/issues/5269)) ([5203da0](https://github.com/ReVanced/revanced-patches/commit/5203da0ae58e467657bc915ab0af5b9904c4f492))
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" hiding unwanted components ([#5292](https://github.com/ReVanced/revanced-patches/issues/5292)) ([d6b1f7a](https://github.com/ReVanced/revanced-patches/commit/d6b1f7a6e18b1c0eb4374c5e22a1c746dcb3a522))
|
||||||
|
* **YouTube - Hide Shorts components:** Fix hiding of untoggled components ([#5266](https://github.com/ReVanced/revanced-patches/issues/5266)) ([008e192](https://github.com/ReVanced/revanced-patches/commit/008e192779a8658e894d5718baa732717bf96e40))
|
||||||
|
* **YouTube - SponsorBlock:** Do not show undo skip if PiP is active ([#5314](https://github.com/ReVanced/revanced-patches/issues/5314)) ([18af8de](https://github.com/ReVanced/revanced-patches/commit/18af8dead2c6c7f0d99cd75b69948240e0bcd12c))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify:** Remove ads section from browse ([#5193](https://github.com/ReVanced/revanced-patches/issues/5193)) ([ebd4dcc](https://github.com/ReVanced/revanced-patches/commit/ebd4dccf12a5fbd31d2d53c19a792c389a4641d7))
|
||||||
|
* **YouTube - Hide layout components:** Add `Hide in history` option to filter bar ([#5271](https://github.com/ReVanced/revanced-patches/issues/5271)) ([ba242a3](https://github.com/ReVanced/revanced-patches/commit/ba242a36b040b82e84870e5e240734637125a472))
|
||||||
|
* **YouTube - SponsorBlock:** Add "Undo automatic skip toast" ([#5277](https://github.com/ReVanced/revanced-patches/issues/5277)) ([7fa169a](https://github.com/ReVanced/revanced-patches/commit/7fa169ae262c880019c5a069a2d6bdc7f94885f1))
|
||||||
|
|
||||||
|
# [5.30.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.9...v5.30.0-dev.10) (2025-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions by simplifying fingerprint ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([dad0ff4](https://github.com/ReVanced/revanced-patches/commit/dad0ff4fba74c2b020fbde6c6d5eb66e10e6f1f7))
|
||||||
|
|
||||||
|
# [5.30.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.8...v5.30.0-dev.9) (2025-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Unlock Premium:** Fix hiding context menu ads on newest versions ([#5318](https://github.com/ReVanced/revanced-patches/issues/5318)) ([73fd832](https://github.com/ReVanced/revanced-patches/commit/73fd83222e089a5fd6e1526e5c12f5a1e9893a35))
|
||||||
|
|
||||||
|
# [5.30.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.7...v5.30.0-dev.8) (2025-07-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Spoof client:** Skip native login screens ([#5228](https://github.com/ReVanced/revanced-patches/issues/5228)) ([c5ebc63](https://github.com/ReVanced/revanced-patches/commit/c5ebc6336ed17cc9cc7f1348282a2aa3c173fb95))
|
||||||
|
|
||||||
|
# [5.30.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.6...v5.30.0-dev.7) (2025-07-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Spoof client:** Handle remaining edge cases to obtain a session ([#5285](https://github.com/ReVanced/revanced-patches/issues/5285)) ([2bb2d59](https://github.com/ReVanced/revanced-patches/commit/2bb2d594936093774e232ad8b274c81e805c5bf6))
|
||||||
|
|
||||||
|
# [5.30.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.5...v5.30.0-dev.6) (2025-07-01)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Do not show undo skip if PiP is active ([#5314](https://github.com/ReVanced/revanced-patches/issues/5314)) ([18af8de](https://github.com/ReVanced/revanced-patches/commit/18af8dead2c6c7f0d99cd75b69948240e0bcd12c))
|
||||||
|
|
||||||
|
# [5.30.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.4...v5.30.0-dev.5) (2025-06-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide ticket shelf" hiding unwanted components ([#5292](https://github.com/ReVanced/revanced-patches/issues/5292)) ([d6b1f7a](https://github.com/ReVanced/revanced-patches/commit/d6b1f7a6e18b1c0eb4374c5e22a1c746dcb3a522))
|
||||||
|
|
||||||
|
# [5.30.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.3...v5.30.0-dev.4) (2025-06-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Add "Undo automatic skip toast" ([#5277](https://github.com/ReVanced/revanced-patches/issues/5277)) ([7fa169a](https://github.com/ReVanced/revanced-patches/commit/7fa169ae262c880019c5a069a2d6bdc7f94885f1))
|
||||||
|
|
||||||
|
# [5.30.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.2...v5.30.0-dev.3) (2025-06-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide AI Comments summary" in Comments ([#5284](https://github.com/ReVanced/revanced-patches/issues/5284)) ([d42370e](https://github.com/ReVanced/revanced-patches/commit/d42370ef71f4608abc64b6ef4a3fb0c5bd5e3eb6))
|
||||||
|
|
||||||
|
# [5.30.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.30.0-dev.1...v5.30.0-dev.2) (2025-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Spoof client patch:** Block sending bad integrity verdicts to potentially fix account suspensions ([#5274](https://github.com/ReVanced/revanced-patches/issues/5274)) ([f7b574c](https://github.com/ReVanced/revanced-patches/commit/f7b574ca79c5a616cfe33a3fc75bd8cf68571f7d))
|
||||||
|
|
||||||
|
# [5.30.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.29.1-dev.1...v5.30.0-dev.1) (2025-06-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Fix "Hide shopping links" ([#5267](https://github.com/ReVanced/revanced-patches/issues/5267)) ([2fe4607](https://github.com/ReVanced/revanced-patches/commit/2fe46079d78ab98076d3a4cdf01c8bfdbdea45c0))
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide AI-generated video summary" in video description ([#5269](https://github.com/ReVanced/revanced-patches/issues/5269)) ([5203da0](https://github.com/ReVanced/revanced-patches/commit/5203da0ae58e467657bc915ab0af5b9904c4f492))
|
||||||
|
* **YouTube - Hide Shorts components:** Fix hiding of untoggled components ([#5266](https://github.com/ReVanced/revanced-patches/issues/5266)) ([008e192](https://github.com/ReVanced/revanced-patches/commit/008e192779a8658e894d5718baa732717bf96e40))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify:** Remove ads section from browse ([#5193](https://github.com/ReVanced/revanced-patches/issues/5193)) ([ebd4dcc](https://github.com/ReVanced/revanced-patches/commit/ebd4dccf12a5fbd31d2d53c19a792c389a4641d7))
|
||||||
|
* **YouTube - Hide layout components:** Add `Hide in history` option to filter bar ([#5271](https://github.com/ReVanced/revanced-patches/issues/5271)) ([ba242a3](https://github.com/ReVanced/revanced-patches/commit/ba242a36b040b82e84870e5e240734637125a472))
|
||||||
|
|
||||||
|
## [5.29.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.29.0...v5.29.1-dev.1) (2025-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify:** Add `Spoof client` patch to fix various issues by using a web platform access token ([#5173](https://github.com/ReVanced/revanced-patches/issues/5173)) ([b7b75bb](https://github.com/ReVanced/revanced-patches/commit/b7b75bb9d8d5fd505121e752b8a20e61ff28d1b2))
|
||||||
|
|
||||||
|
# [5.29.0](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.29.0) (2025-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([e6876d5](https://github.com/ReVanced/revanced-patches/commit/e6876d510d28f6a3a41ec1722a033b3e30a22c65))
|
||||||
|
* **Google Photos:** Resolve startup crash for Android 5.0 devices ([0294533](https://github.com/ReVanced/revanced-patches/commit/0294533c4d9a321aea086eedb4e46385ae9a026e))
|
||||||
|
* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([1e2efad](https://github.com/ReVanced/revanced-patches/commit/1e2efad7b2714c395ed6b0a77cbbf8a2265df520))
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide video description attributes" ([#5250](https://github.com/ReVanced/revanced-patches/issues/5250)) ([2f22d45](https://github.com/ReVanced/revanced-patches/commit/2f22d45eb80745ac64fbea44c8055ebe7925a586))
|
||||||
|
* **YouTube - Hide Shorts components:** Fix "Hide Use this sound button" ([#5233](https://github.com/ReVanced/revanced-patches/issues/5233)) ([5d6ec9e](https://github.com/ReVanced/revanced-patches/commit/5d6ec9e94a6221a0f32762d5bede893e9e7457fc))
|
||||||
|
* **YouTube - Hide Shorts components:** Fix "Hide Use this template button" ([#5249](https://github.com/ReVanced/revanced-patches/issues/5249)) ([b399ecb](https://github.com/ReVanced/revanced-patches/commit/b399ecbb6a222d82dd5e4b3417c9f7eff4324adb))
|
||||||
|
* **YouTube:** Always use single threaded layout to resolve layout bugs in unpatched YouTube ([#5226](https://github.com/ReVanced/revanced-patches/issues/5226)) ([1f539b1](https://github.com/ReVanced/revanced-patches/commit/1f539b1396526b2c767d77a804bd0308ee4a42ec))
|
||||||
|
* **YouTube:** Fix refactoring app startup exception ([1b00c90](https://github.com/ReVanced/revanced-patches/commit/1b00c907f4b90f4659afb4a54ba61ac2835b460d))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Spoof app signature` patch ([#5158](https://github.com/ReVanced/revanced-patches/issues/5158)) ([78b25aa](https://github.com/ReVanced/revanced-patches/commit/78b25aa4e87ec3f9df1d57831b48a39029969416))
|
||||||
|
* **Cricbuzz:** Add `Hide ads` patch ([#4998](https://github.com/ReVanced/revanced-patches/issues/4998)) ([83ccfa8](https://github.com/ReVanced/revanced-patches/commit/83ccfa8e1b5d5a44c55ef659484acf3cc08d3346))
|
||||||
|
* **Crunchyroll:** Add `Hide ads` patch ([#5201](https://github.com/ReVanced/revanced-patches/issues/5201)) ([46b4398](https://github.com/ReVanced/revanced-patches/commit/46b4398fd6ca223391ed8f497a8347c2313421b7))
|
||||||
|
* **YouTube - Hide Shorts components:** Add `Hide Effects button` ([#5255](https://github.com/ReVanced/revanced-patches/issues/5255)) ([240897a](https://github.com/ReVanced/revanced-patches/commit/240897a94008ce9a148c87bb41b978d553d5a6f5))
|
||||||
|
* **YouTube - Hide video action buttons:** Add `Hide Stop ads` ([#5245](https://github.com/ReVanced/revanced-patches/issues/5245)) ([274dcc6](https://github.com/ReVanced/revanced-patches/commit/274dcc676e009be63eb6970de33abacd34dc6560))
|
||||||
|
* **YouTube:** Add an option to disable toasts when changing default playback speed or quality ([#5230](https://github.com/ReVanced/revanced-patches/issues/5230)) ([c68cde3](https://github.com/ReVanced/revanced-patches/commit/c68cde3a896450874cc571be5c4723387db96032))
|
||||||
|
* **YouTube:** Support version `20.13.41` ([#5253](https://github.com/ReVanced/revanced-patches/issues/5253)) ([d284c3d](https://github.com/ReVanced/revanced-patches/commit/d284c3dd3277430b6885e7c27ee02d062dcefc85))
|
||||||
|
|
||||||
|
# [5.29.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.10...v5.29.0-dev.11) (2025-06-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Cricbuzz:** Add `Hide ads` patch ([#4998](https://github.com/ReVanced/revanced-patches/issues/4998)) ([83ccfa8](https://github.com/ReVanced/revanced-patches/commit/83ccfa8e1b5d5a44c55ef659484acf3cc08d3346))
|
||||||
|
|
||||||
|
# [5.29.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.9...v5.29.0-dev.10) (2025-06-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Add `Hide Effects button` ([#5255](https://github.com/ReVanced/revanced-patches/issues/5255)) ([240897a](https://github.com/ReVanced/revanced-patches/commit/240897a94008ce9a148c87bb41b978d553d5a6f5))
|
||||||
|
|
||||||
|
# [5.29.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.8...v5.29.0-dev.9) (2025-06-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Spoof app signature` patch ([#5158](https://github.com/ReVanced/revanced-patches/issues/5158)) ([78b25aa](https://github.com/ReVanced/revanced-patches/commit/78b25aa4e87ec3f9df1d57831b48a39029969416))
|
||||||
|
|
||||||
|
# [5.29.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.7...v5.29.0-dev.8) (2025-06-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Support version `20.13.41` ([#5253](https://github.com/ReVanced/revanced-patches/issues/5253)) ([d284c3d](https://github.com/ReVanced/revanced-patches/commit/d284c3dd3277430b6885e7c27ee02d062dcefc85))
|
||||||
|
|
||||||
|
# [5.29.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.6...v5.29.0-dev.7) (2025-06-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Fix "Hide video description attributes" ([#5250](https://github.com/ReVanced/revanced-patches/issues/5250)) ([2f22d45](https://github.com/ReVanced/revanced-patches/commit/2f22d45eb80745ac64fbea44c8055ebe7925a586))
|
||||||
|
* **YouTube - Hide Shorts components:** Fix "Hide Use this template button" ([#5249](https://github.com/ReVanced/revanced-patches/issues/5249)) ([b399ecb](https://github.com/ReVanced/revanced-patches/commit/b399ecbb6a222d82dd5e4b3417c9f7eff4324adb))
|
||||||
|
|
||||||
|
# [5.29.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.5...v5.29.0-dev.6) (2025-06-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide video action buttons:** Add `Hide Stop ads` ([#5245](https://github.com/ReVanced/revanced-patches/issues/5245)) ([274dcc6](https://github.com/ReVanced/revanced-patches/commit/274dcc676e009be63eb6970de33abacd34dc6560))
|
||||||
|
|
||||||
|
# [5.29.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.4...v5.29.0-dev.5) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Google Photos:** Resolve startup crash for Android 5.0 devices ([0294533](https://github.com/ReVanced/revanced-patches/commit/0294533c4d9a321aea086eedb4e46385ae9a026e))
|
||||||
|
|
||||||
|
# [5.29.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.3...v5.29.0-dev.4) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Fix "Hide Use this sound button" ([#5233](https://github.com/ReVanced/revanced-patches/issues/5233)) ([5d6ec9e](https://github.com/ReVanced/revanced-patches/commit/5d6ec9e94a6221a0f32762d5bede893e9e7457fc))
|
||||||
|
|
||||||
|
# [5.29.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.2...v5.29.0-dev.3) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Fix refactoring app startup exception ([1b00c90](https://github.com/ReVanced/revanced-patches/commit/1b00c907f4b90f4659afb4a54ba61ac2835b460d))
|
||||||
|
|
||||||
|
# [5.29.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.29.0-dev.1...v5.29.0-dev.2) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Crunchyroll:** Add `Hide ads` patch ([#5201](https://github.com/ReVanced/revanced-patches/issues/5201)) ([46b4398](https://github.com/ReVanced/revanced-patches/commit/46b4398fd6ca223391ed8f497a8347c2313421b7))
|
||||||
|
|
||||||
|
# [5.29.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.2...v5.29.0-dev.1) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Always use single threaded layout to resolve layout bugs in unpatched YouTube ([#5226](https://github.com/ReVanced/revanced-patches/issues/5226)) ([1f539b1](https://github.com/ReVanced/revanced-patches/commit/1f539b1396526b2c767d77a804bd0308ee4a42ec))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add an option to disable toasts when changing default playback speed or quality ([#5230](https://github.com/ReVanced/revanced-patches/issues/5230)) ([c68cde3](https://github.com/ReVanced/revanced-patches/commit/c68cde3a896450874cc571be5c4723387db96032))
|
||||||
|
|
||||||
|
## [5.28.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.1...v5.28.1-dev.2) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([1e2efad](https://github.com/ReVanced/revanced-patches/commit/1e2efad7b2714c395ed6b0a77cbbf8a2265df520))
|
||||||
|
|
||||||
|
## [5.28.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.28.1-dev.1) (2025-06-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([e6876d5](https://github.com/ReVanced/revanced-patches/commit/e6876d510d28f6a3a41ec1722a033b3e30a22c65))
|
||||||
|
|
||||||
|
# [5.28.0](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0) (2025-06-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([a93d74d](https://github.com/ReVanced/revanced-patches/commit/a93d74d26e7ef87a3745df2b9fe82722d65a0e59))
|
||||||
|
* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([4ad4887](https://github.com/ReVanced/revanced-patches/commit/4ad488744d87543c31e453dc7b6d8182b3a7f440))
|
||||||
|
* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([ca83047](https://github.com/ReVanced/revanced-patches/commit/ca83047f5c4acbb267d5b98db80ad111999086e0))
|
||||||
|
* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([e7dd061](https://github.com/ReVanced/revanced-patches/commit/e7dd061c513af90861c0ab0d7adc6ee43be57ce2))
|
||||||
|
* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([3558c44](https://github.com/ReVanced/revanced-patches/commit/3558c44a05c13f19fefdbbf14b364181a79f17c0))
|
||||||
|
* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([c9e54e1](https://github.com/ReVanced/revanced-patches/commit/c9e54e1d36243945ac1ec3108fe38edf0e15d772))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([8736b6a](https://github.com/ReVanced/revanced-patches/commit/8736b6a80b48cb1f4562c9f9919804006ddb18bd))
|
||||||
|
* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([312b6dc](https://github.com/ReVanced/revanced-patches/commit/312b6dc04e01c2758cd304ca8606306027aa2f01))
|
||||||
|
|
||||||
|
# [5.28.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.7...v5.28.0-dev.8) (2025-06-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([4ad4887](https://github.com/ReVanced/revanced-patches/commit/4ad488744d87543c31e453dc7b6d8182b3a7f440))
|
||||||
|
|
||||||
|
# [5.28.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.6...v5.28.0-dev.7) (2025-06-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([c9e54e1](https://github.com/ReVanced/revanced-patches/commit/c9e54e1d36243945ac1ec3108fe38edf0e15d772))
|
||||||
|
|
||||||
|
# [5.28.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.5...v5.28.0-dev.6) (2025-06-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([3558c44](https://github.com/ReVanced/revanced-patches/commit/3558c44a05c13f19fefdbbf14b364181a79f17c0))
|
||||||
|
|
||||||
|
# [5.28.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.4...v5.28.0-dev.5) (2025-06-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([ca83047](https://github.com/ReVanced/revanced-patches/commit/ca83047f5c4acbb267d5b98db80ad111999086e0))
|
||||||
|
|
||||||
|
# [5.28.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.3...v5.28.0-dev.4) (2025-06-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([312b6dc](https://github.com/ReVanced/revanced-patches/commit/312b6dc04e01c2758cd304ca8606306027aa2f01))
|
||||||
|
|
||||||
|
# [5.28.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.2...v5.28.0-dev.3) (2025-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([e7dd061](https://github.com/ReVanced/revanced-patches/commit/e7dd061c513af90861c0ab0d7adc6ee43be57ce2))
|
||||||
|
|
||||||
|
# [5.28.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.1...v5.28.0-dev.2) (2025-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([a93d74d](https://github.com/ReVanced/revanced-patches/commit/a93d74d26e7ef87a3745df2b9fe82722d65a0e59))
|
||||||
|
|
||||||
|
# [5.28.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0-dev.1) (2025-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([8736b6a](https://github.com/ReVanced/revanced-patches/commit/8736b6a80b48cb1f4562c9f9919804006ddb18bd))
|
||||||
|
|
||||||
|
# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([863e92b](https://github.com/ReVanced/revanced-patches/commit/863e92b20ad6682f10524e475ed18f879048ecae))
|
||||||
|
* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([0923600](https://github.com/ReVanced/revanced-patches/commit/0923600739a126329fc62100b500216860d7005e))
|
||||||
|
* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([8afbef0](https://github.com/ReVanced/revanced-patches/commit/8afbef01343c1e3e6e7e4a4cec6319aebfa4b11c))
|
||||||
|
* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([9a6a639](https://github.com/ReVanced/revanced-patches/commit/9a6a639c4905b00d6dffb0923c839c8e3ae54d0c))
|
||||||
|
* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([bef0dac](https://github.com/ReVanced/revanced-patches/commit/bef0dacac54caf1ca9511d7bc19b19140ccb4eaf))
|
||||||
|
* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([3113cd6](https://github.com/ReVanced/revanced-patches/commit/3113cd6d092952c8657454452f34c0ae85358ec9))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([92311b8](https://github.com/ReVanced/revanced-patches/commit/92311b8e5675f3d4b80ed690d34b699fb847e3cd))
|
||||||
|
* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([328d232](https://github.com/ReVanced/revanced-patches/commit/328d232fe77406fa93a14768fc66e7b998506fba))
|
||||||
|
* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([9175b23](https://github.com/ReVanced/revanced-patches/commit/9175b23e8360d13c8c1c9c8602ca0b5931d13627))
|
||||||
|
* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([dd8afa2](https://github.com/ReVanced/revanced-patches/commit/dd8afa2b07b50be24d764c0f6ddc9e1bbdb91bf1))
|
||||||
|
* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([ac6b916](https://github.com/ReVanced/revanced-patches/commit/ac6b916c0c212167c4645e2110500dc811b3e54a))
|
||||||
|
* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([42db0c2](https://github.com/ReVanced/revanced-patches/commit/42db0c2e36fefccdbeaa072edcec48b1e05b6270))
|
||||||
|
|
||||||
|
# [5.27.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.8...v5.27.0-dev.9) (2025-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([9175b23](https://github.com/ReVanced/revanced-patches/commit/9175b23e8360d13c8c1c9c8602ca0b5931d13627))
|
||||||
|
|
||||||
|
# [5.27.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.7...v5.27.0-dev.8) (2025-06-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([92311b8](https://github.com/ReVanced/revanced-patches/commit/92311b8e5675f3d4b80ed690d34b699fb847e3cd))
|
||||||
|
|
||||||
|
# [5.27.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.6...v5.27.0-dev.7) (2025-06-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([dd8afa2](https://github.com/ReVanced/revanced-patches/commit/dd8afa2b07b50be24d764c0f6ddc9e1bbdb91bf1))
|
||||||
|
|
||||||
|
# [5.27.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.5...v5.27.0-dev.6) (2025-06-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([ac6b916](https://github.com/ReVanced/revanced-patches/commit/ac6b916c0c212167c4645e2110500dc811b3e54a))
|
||||||
|
|
||||||
|
# [5.27.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.4...v5.27.0-dev.5) (2025-06-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([328d232](https://github.com/ReVanced/revanced-patches/commit/328d232fe77406fa93a14768fc66e7b998506fba))
|
||||||
|
|
||||||
|
# [5.27.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.3...v5.27.0-dev.4) (2025-06-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([863e92b](https://github.com/ReVanced/revanced-patches/commit/863e92b20ad6682f10524e475ed18f879048ecae))
|
||||||
|
|
||||||
|
# [5.27.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.2...v5.27.0-dev.3) (2025-06-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([0923600](https://github.com/ReVanced/revanced-patches/commit/0923600739a126329fc62100b500216860d7005e))
|
||||||
|
|
||||||
|
# [5.27.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.1...v5.27.0-dev.2) (2025-06-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([3113cd6](https://github.com/ReVanced/revanced-patches/commit/3113cd6d092952c8657454452f34c0ae85358ec9))
|
||||||
|
|
||||||
|
# [5.27.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.3...v5.27.0-dev.1) (2025-06-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([42db0c2](https://github.com/ReVanced/revanced-patches/commit/42db0c2e36fefccdbeaa072edcec48b1e05b6270))
|
||||||
|
|
||||||
|
## [5.26.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.2...v5.26.1-dev.3) (2025-06-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([8afbef0](https://github.com/ReVanced/revanced-patches/commit/8afbef01343c1e3e6e7e4a4cec6319aebfa4b11c))
|
||||||
|
|
||||||
|
## [5.26.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.1...v5.26.1-dev.2) (2025-06-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([9a6a639](https://github.com/ReVanced/revanced-patches/commit/9a6a639c4905b00d6dffb0923c839c8e3ae54d0c))
|
||||||
|
|
||||||
|
## [5.26.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.26.1-dev.1) (2025-06-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([bef0dac](https://github.com/ReVanced/revanced-patches/commit/bef0dacac54caf1ca9511d7bc19b19140ccb4eaf))
|
||||||
|
|
||||||
|
# [5.26.0](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0) (2025-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([9357887](https://github.com/ReVanced/revanced-patches/commit/9357887b6fca7aaf34dfb0163645b6a998e1db76))
|
||||||
|
* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([835b7bd](https://github.com/ReVanced/revanced-patches/commit/835b7bd7bd667abd632822c98898972e5124dbb6))
|
||||||
|
* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([8ecacaa](https://github.com/ReVanced/revanced-patches/commit/8ecacaad27162d9380014a9a13ac9220b12257b2))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([b0440ad](https://github.com/ReVanced/revanced-patches/commit/b0440ad6af0e190e516974ce896dcc54c8d2e122))
|
||||||
|
* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([3201681](https://github.com/ReVanced/revanced-patches/commit/32016819d2adbdfdd5e028941d56feda36d20b00))
|
||||||
|
* **Sync for Reddit:** Add `Fix post thumbnails` patch ([e1ec30c](https://github.com/ReVanced/revanced-patches/commit/e1ec30c5b07560a39d7b8ab293b0c1f39fd59ef2))
|
||||||
|
* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([22b9bee](https://github.com/ReVanced/revanced-patches/commit/22b9beedd3243a8d6a5635f591b91cdcf307be37))
|
||||||
|
* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([9a1e6ca](https://github.com/ReVanced/revanced-patches/commit/9a1e6ca178d9833ee2c681fb130b9290a4e89cd8))
|
||||||
|
|
||||||
|
# [5.26.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.7...v5.26.0-dev.8) (2025-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([835b7bd](https://github.com/ReVanced/revanced-patches/commit/835b7bd7bd667abd632822c98898972e5124dbb6))
|
||||||
|
|
||||||
|
# [5.26.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.6...v5.26.0-dev.7) (2025-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([22b9bee](https://github.com/ReVanced/revanced-patches/commit/22b9beedd3243a8d6a5635f591b91cdcf307be37))
|
||||||
|
|
||||||
|
# [5.26.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.5...v5.26.0-dev.6) (2025-06-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Sync for Reddit:** Add `Fix post thumbnails` patch ([e1ec30c](https://github.com/ReVanced/revanced-patches/commit/e1ec30c5b07560a39d7b8ab293b0c1f39fd59ef2))
|
||||||
|
|
||||||
# [5.26.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.4...v5.26.0-dev.5) (2025-06-03)
|
# [5.26.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.4...v5.26.0-dev.5) (2025-06-03)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
build.gradle.kts
Normal file
3
build.gradle.kts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library) apply false
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
4
extensions/cricbuzz/build.gradle.kts
Normal file
4
extensions/cricbuzz/build.gradle.kts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:shared:library"))
|
||||||
|
compileOnly(project(":extensions:cricbuzz:stub"))
|
||||||
|
}
|
||||||
1
extensions/cricbuzz/src/main/AndroidManifest.xml
Normal file
1
extensions/cricbuzz/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package app.revanced.extension.cricbuzz.ads;
|
||||||
|
|
||||||
|
import com.cricbuzz.android.data.rest.model.BottomBar;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class HideAdsPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void filterCb11(List<BottomBar> list) {
|
||||||
|
try {
|
||||||
|
Iterator<BottomBar> iterator = list.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
BottomBar bar = iterator.next();
|
||||||
|
if (bar.getName().equals("Cricbuzz11")) {
|
||||||
|
Logger.printInfo(() -> "Removing Cricbuzz11 bar: " + bar);
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "filterCb11 failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
extensions/cricbuzz/stub/build.gradle.kts
Normal file
17
extensions/cricbuzz/stub/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.android.library)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
compileSdk = 34
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/cricbuzz/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/cricbuzz/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.cricbuzz.android.data.rest.model;
|
||||||
|
|
||||||
|
public final class BottomBar {
|
||||||
|
public final String getName() { throw new UnsupportedOperationException(); }
|
||||||
|
}
|
||||||
@@ -1,14 +1,24 @@
|
|||||||
package app.revanced.extension.messenger.metaai;
|
package app.revanced.extension.messenger.metaai;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class RemoveMetaAIPatch {
|
public class RemoveMetaAIPatch {
|
||||||
public static boolean overrideConfigBool(long id, boolean value) {
|
private static final Set<Long> loggedIDs = Collections.synchronizedSet(new HashSet<>());
|
||||||
// It seems like all configs starting with 363219 are related to Meta AI.
|
|
||||||
// A list of specific ones that need disabling would probably be better,
|
public static boolean overrideBooleanFlag(long id, boolean value) {
|
||||||
// but these config numbers seem to change slightly with each update.
|
try {
|
||||||
// These first 6 digits don't though.
|
if (Long.toString(id).startsWith("REPLACED_BY_PATCH")) {
|
||||||
if (Long.toString(id).startsWith("363219"))
|
if (loggedIDs.add(id))
|
||||||
return false;
|
Logger.printInfo(() -> "Overriding " + id + " from " + value + " to false");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "overrideBooleanFlag failure", ex);
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package app.revanced.extension.primevideo.videoplayer;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.RectF;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
import com.amazon.video.sdk.player.Player;
|
||||||
|
|
||||||
|
public class PlaybackSpeedPatch {
|
||||||
|
private static Player player;
|
||||||
|
private static final float[] SPEED_VALUES = {0.5f, 0.7f, 0.8f, 0.9f, 0.95f, 1.0f, 1.05f, 1.1f, 1.2f, 1.3f, 1.5f, 2.0f};
|
||||||
|
private static final String SPEED_BUTTON_TAG = "speed_overlay";
|
||||||
|
|
||||||
|
public static void setPlayer(Player playerInstance) {
|
||||||
|
player = playerInstance;
|
||||||
|
if (player != null) {
|
||||||
|
// Reset playback rate when switching between episodes to ensure correct display.
|
||||||
|
player.setPlaybackRate(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void initializeSpeedOverlay(View userControlsView) {
|
||||||
|
try {
|
||||||
|
LinearLayout buttonContainer = Utils.getChildViewByResourceName(userControlsView, "ButtonContainerPlayerTop");
|
||||||
|
|
||||||
|
// If the speed overlay exists we should return early.
|
||||||
|
if (Utils.getChildView(buttonContainer, false, child ->
|
||||||
|
child instanceof ImageView && SPEED_BUTTON_TAG.equals(child.getTag())) != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageView speedButton = createSpeedButton(userControlsView.getContext());
|
||||||
|
speedButton.setOnClickListener(v -> changePlaybackSpeed(speedButton));
|
||||||
|
buttonContainer.addView(speedButton, 0);
|
||||||
|
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
Logger.printException(() -> "initializeSpeedOverlay, no button container found", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.printException(() -> "initializeSpeedOverlay failure", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ImageView createSpeedButton(Context context) {
|
||||||
|
ImageView speedButton = new ImageView(context);
|
||||||
|
speedButton.setContentDescription("Playback Speed");
|
||||||
|
speedButton.setTag(SPEED_BUTTON_TAG);
|
||||||
|
speedButton.setClickable(true);
|
||||||
|
speedButton.setFocusable(true);
|
||||||
|
speedButton.setScaleType(ImageView.ScaleType.CENTER);
|
||||||
|
|
||||||
|
SpeedIconDrawable speedIcon = new SpeedIconDrawable();
|
||||||
|
speedButton.setImageDrawable(speedIcon);
|
||||||
|
|
||||||
|
int buttonSize = Utils.dipToPixels(48);
|
||||||
|
speedButton.setMinimumWidth(buttonSize);
|
||||||
|
speedButton.setMinimumHeight(buttonSize);
|
||||||
|
|
||||||
|
return speedButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] getSpeedOptions() {
|
||||||
|
String[] options = new String[SPEED_VALUES.length];
|
||||||
|
for (int i = 0; i < SPEED_VALUES.length; i++) {
|
||||||
|
options[i] = SPEED_VALUES[i] + "x";
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void changePlaybackSpeed(ImageView imageView) {
|
||||||
|
if (player == null) {
|
||||||
|
Logger.printException(() -> "Player not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
player.pause();
|
||||||
|
AlertDialog dialog = createSpeedPlaybackDialog(imageView);
|
||||||
|
dialog.setOnDismissListener(dialogInterface -> player.play());
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.printException(() -> "changePlaybackSpeed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AlertDialog createSpeedPlaybackDialog(ImageView imageView) {
|
||||||
|
Context context = imageView.getContext();
|
||||||
|
int currentSelection = getCurrentSpeedSelection();
|
||||||
|
|
||||||
|
return new AlertDialog.Builder(context)
|
||||||
|
.setTitle("Select Playback Speed")
|
||||||
|
.setSingleChoiceItems(getSpeedOptions(), currentSelection,
|
||||||
|
PlaybackSpeedPatch::handleSpeedSelection)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getCurrentSpeedSelection() {
|
||||||
|
try {
|
||||||
|
float currentRate = player.getPlaybackRate();
|
||||||
|
int index = Arrays.binarySearch(SPEED_VALUES, currentRate);
|
||||||
|
return Math.max(index, 0); // Use slowest speed if not found.
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.printException(() -> "getCurrentSpeedSelection error getting current playback speed", e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void handleSpeedSelection(android.content.DialogInterface dialog, int selectedIndex) {
|
||||||
|
try {
|
||||||
|
float selectedSpeed = SPEED_VALUES[selectedIndex];
|
||||||
|
player.setPlaybackRate(selectedSpeed);
|
||||||
|
player.play();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.printException(() -> "handleSpeedSelection error setting playback speed", e);
|
||||||
|
} finally {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SpeedIconDrawable extends Drawable {
|
||||||
|
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(Canvas canvas) {
|
||||||
|
int w = getBounds().width();
|
||||||
|
int h = getBounds().height();
|
||||||
|
float centerX = w / 2f;
|
||||||
|
// Position gauge in lower portion.
|
||||||
|
float centerY = h * 0.7f;
|
||||||
|
float radius = Math.min(w, h) / 2f * 0.8f;
|
||||||
|
|
||||||
|
paint.setColor(Color.WHITE);
|
||||||
|
paint.setStyle(Paint.Style.STROKE);
|
||||||
|
paint.setStrokeWidth(radius * 0.1f);
|
||||||
|
|
||||||
|
// Draw semicircle.
|
||||||
|
RectF oval = new RectF(centerX - radius, centerY - radius, centerX + radius, centerY + radius);
|
||||||
|
canvas.drawArc(oval, 180, 180, false, paint);
|
||||||
|
|
||||||
|
// Draw three tick marks.
|
||||||
|
paint.setStrokeWidth(radius * 0.06f);
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
float angle = 180 + (i * 45); // 180°, 225°, 270°.
|
||||||
|
float angleRad = (float) Math.toRadians(angle);
|
||||||
|
|
||||||
|
float startX = centerX + (radius * 0.8f) * (float) Math.cos(angleRad);
|
||||||
|
float startY = centerY + (radius * 0.8f) * (float) Math.sin(angleRad);
|
||||||
|
float endX = centerX + radius * (float) Math.cos(angleRad);
|
||||||
|
float endY = centerY + radius * (float) Math.sin(angleRad);
|
||||||
|
|
||||||
|
canvas.drawLine(startX, startY, endX, endY, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw needle.
|
||||||
|
paint.setStrokeWidth(radius * 0.08f);
|
||||||
|
float needleAngle = 200; // Slightly right of center.
|
||||||
|
float needleAngleRad = (float) Math.toRadians(needleAngle);
|
||||||
|
|
||||||
|
float needleEndX = centerX + (radius * 0.6f) * (float) Math.cos(needleAngleRad);
|
||||||
|
float needleEndY = centerY + (radius * 0.6f) * (float) Math.sin(needleAngleRad);
|
||||||
|
|
||||||
|
canvas.drawLine(centerX, centerY, needleEndX, needleEndY, paint);
|
||||||
|
|
||||||
|
// Center dot.
|
||||||
|
paint.setStyle(Paint.Style.FILL);
|
||||||
|
canvas.drawCircle(centerX, centerY, radius * 0.06f, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
paint.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(ColorFilter colorFilter) {
|
||||||
|
paint.setColorFilter(colorFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return PixelFormat.TRANSLUCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntrinsicWidth() {
|
||||||
|
return Utils.dipToPixels(32);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getIntrinsicHeight() {
|
||||||
|
return Utils.dipToPixels(32);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -4,4 +4,10 @@ public interface VideoPlayer {
|
|||||||
long getCurrentPosition();
|
long getCurrentPosition();
|
||||||
|
|
||||||
void seekTo(long positionMs);
|
void seekTo(long positionMs);
|
||||||
|
|
||||||
|
void pause();
|
||||||
|
|
||||||
|
void play();
|
||||||
|
|
||||||
|
boolean isPlaying();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.amazon.video.sdk.player;
|
||||||
|
|
||||||
|
public interface Player {
|
||||||
|
float getPlaybackRate();
|
||||||
|
|
||||||
|
void setPlaybackRate(float rate);
|
||||||
|
|
||||||
|
void play();
|
||||||
|
|
||||||
|
void pause();
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id("com.android.library")
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import static app.revanced.extension.shared.requests.Route.Method.GET;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.app.SearchManager;
|
import android.app.SearchManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@@ -15,9 +15,10 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
@@ -78,13 +79,27 @@ public class GmsCoreSupport {
|
|||||||
// Use a delay to allow the activity to finish initializing.
|
// Use a delay to allow the activity to finish initializing.
|
||||||
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
|
// Create the custom dialog.
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
str("gms_core_dialog_title"), // Title.
|
||||||
|
str(dialogMessageRef), // Message.
|
||||||
|
null, // No EditText.
|
||||||
|
str(positiveButtonTextRef), // OK button text.
|
||||||
|
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
|
||||||
|
null, // No Cancel button action.
|
||||||
|
null, // No Neutral button text.
|
||||||
|
null, // No Neutral button action.
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
Dialog dialog = dialogPair.first;
|
||||||
|
|
||||||
// Do not set cancelable to false, to allow using back button to skip the action,
|
// Do not set cancelable to false, to allow using back button to skip the action,
|
||||||
// just in case the battery change can never be satisfied.
|
// just in case the battery change can never be satisfied.
|
||||||
var dialog = new AlertDialog.Builder(context)
|
dialog.setCancelable(true);
|
||||||
.setTitle(str("gms_core_dialog_title"))
|
|
||||||
.setMessage(str(dialogMessageRef))
|
// Show the dialog
|
||||||
.setPositiveButton(str(positiveButtonTextRef), onPositiveClickListener)
|
|
||||||
.create();
|
|
||||||
Utils.showDialog(context, dialog);
|
Utils.showDialog(context, dialog);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@@ -92,7 +107,6 @@ public class GmsCoreSupport {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
public static void checkGmsCore(Activity context) {
|
public static void checkGmsCore(Activity context) {
|
||||||
try {
|
try {
|
||||||
// Verify the user has not included GmsCore for a root installation.
|
// Verify the user has not included GmsCore for a root installation.
|
||||||
@@ -140,7 +154,9 @@ public class GmsCoreSupport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if GmsCore is currently running in the background.
|
// Check if GmsCore is currently running in the background.
|
||||||
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER);
|
||||||
|
//noinspection TryFinallyCanBeTryWithResources
|
||||||
|
try {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||||
checkIfDontKillMyAppSupportsManufacturer();
|
checkIfDontKillMyAppSupportsManufacturer();
|
||||||
@@ -150,6 +166,8 @@ public class GmsCoreSupport {
|
|||||||
"gms_core_dialog_open_website_text",
|
"gms_core_dialog_open_website_text",
|
||||||
(dialog, id) -> openDontKillMyApp());
|
(dialog, id) -> openDontKillMyApp());
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
if (client != null) client.close();
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "checkGmsCore failure", ex);
|
Logger.printException(() -> "checkGmsCore failure", ex);
|
||||||
@@ -209,6 +227,11 @@ public class GmsCoreSupport {
|
|||||||
* @return If GmsCore is not whitelisted from battery optimizations.
|
* @return If GmsCore is not whitelisted from battery optimizations.
|
||||||
*/
|
*/
|
||||||
private static boolean batteryOptimizationsEnabled(Context context) {
|
private static boolean batteryOptimizationsEnabled(Context context) {
|
||||||
|
//noinspection ObsoleteSdkInt
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||||
|
// Android 5.0 does not have battery optimization settings.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import app.revanced.extension.shared.settings.preference.LogBufferManager;
|
|||||||
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
|
||||||
* and additionally accessible thru {@link LogBufferManager}.
|
* and additionally accessible thru {@link LogBufferManager}.
|
||||||
*
|
*
|
||||||
* All methods are thread safe.
|
* All methods are thread safe, and are safe to call even
|
||||||
|
* if {@link Utils#getContext()} is not available.
|
||||||
*/
|
*/
|
||||||
public class Logger {
|
public class Logger {
|
||||||
|
|
||||||
@@ -138,6 +139,20 @@ public class Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean shouldLogDebug() {
|
||||||
|
// If the app is still starting up and the context is not yet set,
|
||||||
|
// then allow debug logging regardless what the debug setting actually is.
|
||||||
|
return Utils.context == null || DEBUG.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean shouldShowErrorToast() {
|
||||||
|
return Utils.context != null && DEBUG_TOAST_ON_ERROR.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean includeStackTrace() {
|
||||||
|
return Utils.context != null && DEBUG_STACKTRACE.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs debug messages under the outer class name of the code calling this method.
|
* Logs debug messages under the outer class name of the code calling this method.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -157,8 +172,8 @@ public class Logger {
|
|||||||
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
||||||
*/
|
*/
|
||||||
public static void printDebug(LogMessage message, @Nullable Exception ex) {
|
public static void printDebug(LogMessage message, @Nullable Exception ex) {
|
||||||
if (DEBUG.get()) {
|
if (shouldLogDebug()) {
|
||||||
logInternal(LogLevel.DEBUG, message, ex, DEBUG_STACKTRACE.get(), false);
|
logInternal(LogLevel.DEBUG, message, ex, includeStackTrace(), false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +188,7 @@ public class Logger {
|
|||||||
* Logs information messages using the outer class name of the code calling this method.
|
* Logs information messages using the outer class name of the code calling this method.
|
||||||
*/
|
*/
|
||||||
public static void printInfo(LogMessage message, @Nullable Exception ex) {
|
public static void printInfo(LogMessage message, @Nullable Exception ex) {
|
||||||
logInternal(LogLevel.INFO, message, ex, DEBUG_STACKTRACE.get(), false);
|
logInternal(LogLevel.INFO, message, ex, includeStackTrace(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,22 +209,6 @@ public class Logger {
|
|||||||
* @param ex exception (optional)
|
* @param ex exception (optional)
|
||||||
*/
|
*/
|
||||||
public static void printException(LogMessage message, @Nullable Throwable ex) {
|
public static void printException(LogMessage message, @Nullable Throwable ex) {
|
||||||
logInternal(LogLevel.ERROR, message, ex, DEBUG_STACKTRACE.get(), DEBUG_TOAST_ON_ERROR.get());
|
logInternal(LogLevel.ERROR, message, ex, includeStackTrace(), shouldShowErrorToast());
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
|
|
||||||
* Normally this method should not be used.
|
|
||||||
*/
|
|
||||||
public static void initializationInfo(LogMessage message) {
|
|
||||||
logInternal(LogLevel.INFO, message, null, false, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
|
|
||||||
* Normally this method should not be used.
|
|
||||||
*/
|
|
||||||
public static void initializationException(LogMessage message, @Nullable Exception ex) {
|
|
||||||
logInternal(LogLevel.ERROR, message, ex, false, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,15 +3,21 @@ package app.revanced.extension.shared.checks;
|
|||||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
|
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.PorterDuff;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@@ -86,38 +92,58 @@ abstract class Check {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
AlertDialog alert = new AlertDialog.Builder(activity)
|
// Create the custom dialog.
|
||||||
.setCancelable(false)
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
.setIconAttribute(android.R.attr.alertDialogIcon)
|
activity,
|
||||||
.setTitle(str("revanced_check_environment_failed_title"))
|
str("revanced_check_environment_failed_title"), // Title.
|
||||||
.setMessage(message)
|
message, // Message.
|
||||||
.setPositiveButton(
|
null, // No EditText.
|
||||||
" ",
|
str("revanced_check_environment_dialog_open_official_source_button"), // OK button text.
|
||||||
(dialog, which) -> {
|
() -> {
|
||||||
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
|
// Action for the OK (website) button.
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
|
||||||
activity.startActivity(intent);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
activity.startActivity(intent);
|
||||||
|
|
||||||
// Shutdown to prevent the user from navigating back to this app,
|
// Shutdown to prevent the user from navigating back to this app,
|
||||||
// which is no longer showing a warning dialog.
|
// which is no longer showing a warning dialog.
|
||||||
activity.finishAffinity();
|
activity.finishAffinity();
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
},
|
||||||
).setNegativeButton(
|
null, // No cancel button.
|
||||||
" ",
|
str("revanced_check_environment_dialog_ignore_button"), // Neutral button text.
|
||||||
(dialog, which) -> {
|
() -> {
|
||||||
// Cleanup data if the user incorrectly imported a huge negative number.
|
// Neutral button action.
|
||||||
final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
|
// Cleanup data if the user incorrectly imported a huge negative number.
|
||||||
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1);
|
final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
|
||||||
|
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1);
|
||||||
|
},
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
dialog.dismiss();
|
// Get the dialog and main layout.
|
||||||
}
|
Dialog dialog = dialogPair.first;
|
||||||
).create();
|
LinearLayout mainLayout = dialogPair.second;
|
||||||
|
|
||||||
Utils.showDialog(activity, alert, false, new DialogFragmentOnStartAction() {
|
// Add icon to the dialog.
|
||||||
|
ImageView iconView = new ImageView(activity);
|
||||||
|
iconView.setImageResource(Utils.getResourceIdentifier("revanced_ic_dialog_alert", "drawable"));
|
||||||
|
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
|
||||||
|
iconView.setPadding(0, 0, 0, 0);
|
||||||
|
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
iconParams.gravity = Gravity.CENTER;
|
||||||
|
mainLayout.addView(iconView, 0); // Add icon at the top.
|
||||||
|
|
||||||
|
dialog.setCancelable(false);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
Utils.showDialog(activity, dialog, false, new DialogFragmentOnStartAction() {
|
||||||
boolean hasRun;
|
boolean hasRun;
|
||||||
@Override
|
@Override
|
||||||
public void onStart(AlertDialog dialog) {
|
public void onStart(Dialog dialog) {
|
||||||
// Only run this once, otherwise if the user changes to a different app
|
// Only run this once, otherwise if the user changes to a different app
|
||||||
// then changes back, this handler will run again and disable the buttons.
|
// then changes back, this handler will run again and disable the buttons.
|
||||||
if (hasRun) {
|
if (hasRun) {
|
||||||
@@ -125,19 +151,43 @@ abstract class Check {
|
|||||||
}
|
}
|
||||||
hasRun = true;
|
hasRun = true;
|
||||||
|
|
||||||
var openWebsiteButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
|
// Get the button container to access buttons.
|
||||||
|
LinearLayout buttonContainer = (LinearLayout) mainLayout.getChildAt(mainLayout.getChildCount() - 1);
|
||||||
|
|
||||||
|
Button openWebsiteButton;
|
||||||
|
Button ignoreButton;
|
||||||
|
|
||||||
|
// Check if buttons are in a single-row layout (buttonContainer has one child: rowContainer).
|
||||||
|
if (buttonContainer.getChildCount() == 1 && buttonContainer.getChildAt(0) instanceof LinearLayout) {
|
||||||
|
LinearLayout rowContainer = (LinearLayout) buttonContainer.getChildAt(0);
|
||||||
|
// Neutral button is the first child (index 0).
|
||||||
|
ignoreButton = (Button) rowContainer.getChildAt(0);
|
||||||
|
// OK button is the last child.
|
||||||
|
openWebsiteButton = (Button) rowContainer.getChildAt(rowContainer.getChildCount() - 1);
|
||||||
|
} else {
|
||||||
|
// Multi-row layout: buttons are in separate containers, ordered OK, Cancel, Neutral.
|
||||||
|
LinearLayout okContainer =
|
||||||
|
(LinearLayout) buttonContainer.getChildAt(0); // OK is first.
|
||||||
|
openWebsiteButton = (Button) okContainer.getChildAt(0);
|
||||||
|
LinearLayout neutralContainer =
|
||||||
|
(LinearLayout)buttonContainer.getChildAt(buttonContainer.getChildCount() - 1); // Neutral is last.
|
||||||
|
ignoreButton = (Button) neutralContainer.getChildAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initially set buttons to INVISIBLE and disabled.
|
||||||
|
openWebsiteButton.setVisibility(View.INVISIBLE);
|
||||||
openWebsiteButton.setEnabled(false);
|
openWebsiteButton.setEnabled(false);
|
||||||
|
ignoreButton.setVisibility(View.INVISIBLE);
|
||||||
|
ignoreButton.setEnabled(false);
|
||||||
|
|
||||||
var dismissButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE);
|
// Start the countdown for showing and enabling buttons.
|
||||||
dismissButton.setEnabled(false);
|
getCountdownRunnable(ignoreButton, openWebsiteButton).run();
|
||||||
|
|
||||||
getCountdownRunnable(dismissButton, openWebsiteButton).run();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs.
|
}, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs.
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Runnable getCountdownRunnable(Button dismissButton, Button openWebsiteButton) {
|
private static Runnable getCountdownRunnable(Button ignoreButton, Button openWebsiteButton) {
|
||||||
return new Runnable() {
|
return new Runnable() {
|
||||||
private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON;
|
private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON;
|
||||||
|
|
||||||
@@ -146,17 +196,15 @@ abstract class Check {
|
|||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
|
|
||||||
if (secondsRemaining > 0) {
|
if (secondsRemaining > 0) {
|
||||||
if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON == 0) {
|
if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON <= 0) {
|
||||||
openWebsiteButton.setText(str("revanced_check_environment_dialog_open_official_source_button"));
|
openWebsiteButton.setVisibility(View.VISIBLE);
|
||||||
openWebsiteButton.setEnabled(true);
|
openWebsiteButton.setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
secondsRemaining--;
|
secondsRemaining--;
|
||||||
|
|
||||||
Utils.runOnMainThreadDelayed(this, 1000);
|
Utils.runOnMainThreadDelayed(this, 1000);
|
||||||
} else {
|
} else {
|
||||||
dismissButton.setText(str("revanced_check_environment_dialog_ignore_button"));
|
ignoreButton.setVisibility(View.VISIBLE);
|
||||||
dismissButton.setEnabled(true);
|
ignoreButton.setEnabled(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ public class Route {
|
|||||||
|
|
||||||
private int countMatches(CharSequence seq, char c) {
|
private int countMatches(CharSequence seq, char c) {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < seq.length(); i++) {
|
for (int i = 0, length = seq.length(); i < length; i++) {
|
||||||
if (seq.charAt(i) == c)
|
if (seq.charAt(i) == c)
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,15 +71,20 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
|||||||
json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH));
|
json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
/**
|
||||||
private T getEnumFromString(String enumName) {
|
* @param enumName Enum name. Casing does not matter.
|
||||||
|
* @return Enum of this type with the same declared name.
|
||||||
|
* @throws IllegalArgumentException if the name is not a valid enum of this type.
|
||||||
|
*/
|
||||||
|
protected T getEnumFromString(String enumName) {
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
|
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
|
||||||
if (value.name().equalsIgnoreCase(enumName)) {
|
if (value.name().equalsIgnoreCase(enumName)) {
|
||||||
// noinspection unchecked
|
//noinspection unchecked
|
||||||
return (T) value;
|
return (T) value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("Unknown enum value: " + enumName);
|
throw new IllegalArgumentException("Unknown enum value: " + enumName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +108,9 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
|||||||
* Availability based on if this setting is currently set to any of the provided types.
|
* Availability based on if this setting is currently set to any of the provided types.
|
||||||
*/
|
*/
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public final Setting.Availability availability(@NonNull T... types) {
|
public final Setting.Availability availability(T... types) {
|
||||||
|
Objects.requireNonNull(types);
|
||||||
|
|
||||||
return () -> {
|
return () -> {
|
||||||
T currentEnumType = get();
|
T currentEnumType = get();
|
||||||
for (T enumType : types) {
|
for (T enumType : types) {
|
||||||
|
|||||||
@@ -28,16 +28,14 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* Availability based on a single parent setting being enabled.
|
* Availability based on a single parent setting being enabled.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
public static Availability parent(BooleanSetting parent) {
|
||||||
public static Availability parent(@NonNull BooleanSetting parent) {
|
|
||||||
return parent::get;
|
return parent::get;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Availability based on all parents being enabled.
|
* Availability based on all parents being enabled.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
public static Availability parentsAll(BooleanSetting... parents) {
|
||||||
public static Availability parentsAll(@NonNull BooleanSetting... parents) {
|
|
||||||
return () -> {
|
return () -> {
|
||||||
for (BooleanSetting parent : parents) {
|
for (BooleanSetting parent : parents) {
|
||||||
if (!parent.get()) return false;
|
if (!parent.get()) return false;
|
||||||
@@ -49,8 +47,7 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* Availability based on any parent being enabled.
|
* Availability based on any parent being enabled.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
public static Availability parentsAny(BooleanSetting... parents) {
|
||||||
public static Availability parentsAny(@NonNull BooleanSetting... parents) {
|
|
||||||
return () -> {
|
return () -> {
|
||||||
for (BooleanSetting parent : parents) {
|
for (BooleanSetting parent : parents) {
|
||||||
if (parent.get()) return true;
|
if (parent.get()) return true;
|
||||||
@@ -79,7 +76,7 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}.
|
* Adds a callback for {@link #importFromJSON(Context, String)} and {@link #exportToJson(Context)}.
|
||||||
*/
|
*/
|
||||||
public static void addImportExportCallback(@NonNull ImportExportCallback callback) {
|
public static void addImportExportCallback(ImportExportCallback callback) {
|
||||||
importExportCallbacks.add(Objects.requireNonNull(callback));
|
importExportCallbacks.add(Objects.requireNonNull(callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,14 +97,13 @@ public abstract class Setting<T> {
|
|||||||
public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs");
|
public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs");
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static Setting<?> getSettingFromPath(@NonNull String str) {
|
public static Setting<?> getSettingFromPath(String str) {
|
||||||
return PATH_TO_SETTINGS.get(str);
|
return PATH_TO_SETTINGS.get(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return All settings that have been created.
|
* @return All settings that have been created.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
public static List<Setting<?>> allLoadedSettings() {
|
public static List<Setting<?>> allLoadedSettings() {
|
||||||
return Collections.unmodifiableList(SETTINGS);
|
return Collections.unmodifiableList(SETTINGS);
|
||||||
}
|
}
|
||||||
@@ -115,7 +111,6 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* @return All settings that have been created, sorted by keys.
|
* @return All settings that have been created, sorted by keys.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
private static List<Setting<?>> allLoadedSettingsSorted() {
|
private static List<Setting<?>> allLoadedSettingsSorted() {
|
||||||
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
|
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
|
||||||
return allLoadedSettings();
|
return allLoadedSettings();
|
||||||
@@ -124,13 +119,11 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* The key used to store the value in the shared preferences.
|
* The key used to store the value in the shared preferences.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
public final String key;
|
public final String key;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default value of the setting.
|
* The default value of the setting.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
public final T defaultValue;
|
public final T defaultValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -161,7 +154,6 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* The value of the setting.
|
* The value of the setting.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
|
||||||
protected volatile T value;
|
protected volatile T value;
|
||||||
|
|
||||||
public Setting(String key, T defaultValue) {
|
public Setting(String key, T defaultValue) {
|
||||||
@@ -199,8 +191,8 @@ public abstract class Setting<T> {
|
|||||||
* @param userDialogMessage Confirmation message to display, if the user tries to change the setting from the default value.
|
* @param userDialogMessage Confirmation message to display, if the user tries to change the setting from the default value.
|
||||||
* @param availability Condition that must be true, for this setting to be available to configure.
|
* @param availability Condition that must be true, for this setting to be available to configure.
|
||||||
*/
|
*/
|
||||||
public Setting(@NonNull String key,
|
public Setting(String key,
|
||||||
@NonNull T defaultValue,
|
T defaultValue,
|
||||||
boolean rebootApp,
|
boolean rebootApp,
|
||||||
boolean includeWithImportExport,
|
boolean includeWithImportExport,
|
||||||
@Nullable String userDialogMessage,
|
@Nullable String userDialogMessage,
|
||||||
@@ -227,7 +219,7 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
|
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
|
||||||
*/
|
*/
|
||||||
public static <T> void migrateOldSettingToNew(@NonNull Setting<T> oldSetting, @NonNull Setting<T> newSetting) {
|
public static <T> void migrateOldSettingToNew(Setting<T> oldSetting, Setting<T> newSetting) {
|
||||||
if (oldSetting == newSetting) throw new IllegalArgumentException();
|
if (oldSetting == newSetting) throw new IllegalArgumentException();
|
||||||
|
|
||||||
if (!oldSetting.isSetToDefault()) {
|
if (!oldSetting.isSetToDefault()) {
|
||||||
@@ -243,7 +235,7 @@ public abstract class Setting<T> {
|
|||||||
* This method will be deleted in the future.
|
* This method will be deleted in the future.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
|
public static void migrateFromOldPreferences(SharedPrefCategory oldPrefs, Setting setting, String settingKey) {
|
||||||
if (!oldPrefs.preferences.contains(settingKey)) {
|
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||||
return; // Nothing to do.
|
return; // Nothing to do.
|
||||||
}
|
}
|
||||||
@@ -285,7 +277,7 @@ public abstract class Setting<T> {
|
|||||||
* This intentionally is a static method to deter
|
* This intentionally is a static method to deter
|
||||||
* accidental usage when {@link #save(Object)} was intended.
|
* accidental usage when {@link #save(Object)} was intended.
|
||||||
*/
|
*/
|
||||||
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
|
public static void privateSetValueFromString(Setting<?> setting, String newValue) {
|
||||||
setting.setValueFromString(newValue);
|
setting.setValueFromString(newValue);
|
||||||
|
|
||||||
// Clear the preference value since default is used, to allow changing
|
// Clear the preference value since default is used, to allow changing
|
||||||
@@ -299,7 +291,7 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* Sets the value of {@link #value}, but do not save to {@link #preferences}.
|
* Sets the value of {@link #value}, but do not save to {@link #preferences}.
|
||||||
*/
|
*/
|
||||||
protected abstract void setValueFromString(@NonNull String newValue);
|
protected abstract void setValueFromString(String newValue);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load and set the value of {@link #value}.
|
* Load and set the value of {@link #value}.
|
||||||
@@ -309,7 +301,7 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* Persistently saves the value.
|
* Persistently saves the value.
|
||||||
*/
|
*/
|
||||||
public final void save(@NonNull T newValue) {
|
public final void save(T newValue) {
|
||||||
if (value.equals(newValue)) {
|
if (value.equals(newValue)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -406,7 +398,6 @@ public abstract class Setting<T> {
|
|||||||
json.put(importExportKey, value);
|
json.put(importExportKey, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static String exportToJson(@Nullable Context alertDialogContext) {
|
public static String exportToJson(@Nullable Context alertDialogContext) {
|
||||||
try {
|
try {
|
||||||
JSONObject json = new JSONObject();
|
JSONObject json = new JSONObject();
|
||||||
@@ -445,7 +436,7 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* @return if any settings that require a reboot were changed.
|
* @return if any settings that require a reboot were changed.
|
||||||
*/
|
*/
|
||||||
public static boolean importFromJSON(@NonNull Context alertDialogContext, @NonNull String settingsJsonString) {
|
public static boolean importFromJSON(Context alertDialogContext, String settingsJsonString) {
|
||||||
try {
|
try {
|
||||||
if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
|
if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
|
||||||
settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces
|
settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces
|
||||||
|
|||||||
@@ -3,12 +3,20 @@ package app.revanced.extension.shared.settings.preference;
|
|||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.*;
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceFragment;
|
||||||
|
import android.preference.PreferenceGroup;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
|
import android.preference.ListPreference;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@@ -44,7 +52,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
* Set by subclasses if Strings cannot be added as a resource.
|
* Set by subclasses if Strings cannot be added as a resource.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
|
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle, restartDialogMessage;
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||||
try {
|
try {
|
||||||
@@ -76,7 +84,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
|
|
||||||
updatingPreference = true;
|
updatingPreference = true;
|
||||||
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
||||||
// Updating here can can cause a recursive call back into this same method.
|
// Updating here can cause a recursive call back into this same method.
|
||||||
updatePreference(pref, setting, true, settingImportInProgress);
|
updatePreference(pref, setting, true, settingImportInProgress);
|
||||||
// Update any other preference availability that may now be different.
|
// Update any other preference availability that may now be different.
|
||||||
updateUIAvailability();
|
updateUIAvailability();
|
||||||
@@ -116,11 +124,14 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
|
|
||||||
showingUserDialogMessage = true;
|
showingUserDialogMessage = true;
|
||||||
|
|
||||||
new AlertDialog.Builder(context)
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
.setTitle(confirmDialogTitle)
|
context,
|
||||||
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
|
confirmDialogTitle, // Title.
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
Objects.requireNonNull(setting.userDialogMessage).toString(), // No message.
|
||||||
// User confirmed, save to the Setting.
|
null, // No EditText.
|
||||||
|
null, // OK button text.
|
||||||
|
() -> {
|
||||||
|
// OK button action. User confirmed, save to the Setting.
|
||||||
updatePreference(pref, setting, true, false);
|
updatePreference(pref, setting, true, false);
|
||||||
|
|
||||||
// Update availability of other preferences that may be changed.
|
// Update availability of other preferences that may be changed.
|
||||||
@@ -129,23 +140,27 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
if (setting.rebootApp) {
|
if (setting.rebootApp) {
|
||||||
showRestartDialog(context);
|
showRestartDialog(context);
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
() -> {
|
||||||
// Restore whatever the setting was before the change.
|
// Cancel button action. Restore whatever the setting was before the change.
|
||||||
updatePreference(pref, setting, true, true);
|
updatePreference(pref, setting, true, true);
|
||||||
})
|
},
|
||||||
.setOnDismissListener(dialog -> {
|
null, // No Neutral button.
|
||||||
showingUserDialogMessage = false;
|
null, // No Neutral button action.
|
||||||
})
|
true // Dismiss dialog when onNeutralClick.
|
||||||
.setCancelable(false)
|
);
|
||||||
.show();
|
|
||||||
|
dialogPair.first.setOnDismissListener(d -> showingUserDialogMessage = false);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates all Preferences values and their availability using the current values in {@link Setting}.
|
* Updates all Preferences values and their availability using the current values in {@link Setting}.
|
||||||
*/
|
*/
|
||||||
protected void updateUIToSettingValues() {
|
protected void updateUIToSettingValues() {
|
||||||
updatePreferenceScreen(getPreferenceScreen(), true,true);
|
updatePreferenceScreen(getPreferenceScreen(), true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -280,17 +295,27 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
if (restartDialogTitle == null) {
|
if (restartDialogTitle == null) {
|
||||||
restartDialogTitle = str("revanced_settings_restart_title");
|
restartDialogTitle = str("revanced_settings_restart_title");
|
||||||
}
|
}
|
||||||
|
if (restartDialogMessage == null) {
|
||||||
|
restartDialogMessage = str("revanced_settings_restart_dialog_message");
|
||||||
|
}
|
||||||
if (restartDialogButtonText == null) {
|
if (restartDialogButtonText == null) {
|
||||||
restartDialogButtonText = str("revanced_settings_restart");
|
restartDialogButtonText = str("revanced_settings_restart");
|
||||||
}
|
}
|
||||||
|
|
||||||
new AlertDialog.Builder(context)
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(context,
|
||||||
.setMessage(restartDialogTitle)
|
restartDialogTitle, // Title.
|
||||||
.setPositiveButton(restartDialogButtonText, (dialog, id)
|
restartDialogMessage, // Message.
|
||||||
-> Utils.restartApp(context))
|
null, // No EditText.
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
restartDialogButtonText, // OK button text.
|
||||||
.setCancelable(false)
|
() -> Utils.restartApp(context), // OK button action.
|
||||||
.show();
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
null, // No Neutral button text.
|
||||||
|
null, // No Neutral button action.
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("ResourceType")
|
@SuppressLint("ResourceType")
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package app.revanced.extension.shared.settings.preference;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
@@ -18,14 +19,12 @@ import android.text.TextWatcher;
|
|||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.RelativeSizeSpan;
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
import android.widget.Button;
|
import android.widget.*;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
|
|
||||||
@@ -182,7 +181,7 @@ public class ColorPickerPreference extends EditTextPreference {
|
|||||||
* @throws IllegalArgumentException If the color string is invalid.
|
* @throws IllegalArgumentException If the color string is invalid.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final void setText(String colorString) {
|
public final void setText(String colorString) {
|
||||||
try {
|
try {
|
||||||
Logger.printDebug(() -> "setText: " + colorString);
|
Logger.printDebug(() -> "setText: " + colorString);
|
||||||
super.setText(colorString);
|
super.setText(colorString);
|
||||||
@@ -216,86 +215,6 @@ public class ColorPickerPreference extends EditTextPreference {
|
|||||||
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
|
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a layout with a color preview and EditText for hex color input.
|
|
||||||
*
|
|
||||||
* @param context The context for creating the layout.
|
|
||||||
* @return A LinearLayout containing the color preview and EditText.
|
|
||||||
*/
|
|
||||||
private LinearLayout createDialogLayout(Context context) {
|
|
||||||
LinearLayout layout = new LinearLayout(context);
|
|
||||||
layout.setOrientation(LinearLayout.VERTICAL);
|
|
||||||
layout.setPadding(70, 0, 70, 0);
|
|
||||||
|
|
||||||
// Inflate color picker.
|
|
||||||
View colorPicker = LayoutInflater.from(context).inflate(
|
|
||||||
getResourceIdentifier("revanced_color_picker", "layout"), null);
|
|
||||||
dialogColorPickerView = colorPicker.findViewById(
|
|
||||||
getResourceIdentifier("color_picker_view", "id"));
|
|
||||||
dialogColorPickerView.setColor(currentColor);
|
|
||||||
layout.addView(colorPicker);
|
|
||||||
|
|
||||||
// Horizontal layout for preview and EditText.
|
|
||||||
LinearLayout inputLayout = new LinearLayout(context);
|
|
||||||
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
|
|
||||||
inputLayout.setPadding(0, 20, 0, 0);
|
|
||||||
|
|
||||||
dialogColorPreview = new TextView(context);
|
|
||||||
inputLayout.addView(dialogColorPreview);
|
|
||||||
updateColorPreview();
|
|
||||||
|
|
||||||
EditText editText = getEditText();
|
|
||||||
ViewParent parent = editText.getParent();
|
|
||||||
if (parent instanceof ViewGroup parentViewGroup) {
|
|
||||||
parentViewGroup.removeView(editText);
|
|
||||||
}
|
|
||||||
editText.setLayoutParams(new LinearLayout.LayoutParams(
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
||||||
LinearLayout.LayoutParams.WRAP_CONTENT
|
|
||||||
));
|
|
||||||
String currentColorString = getColorString(currentColor);
|
|
||||||
editText.setText(currentColorString);
|
|
||||||
editText.setSelection(currentColorString.length());
|
|
||||||
editText.setTypeface(Typeface.MONOSPACE);
|
|
||||||
colorTextWatcher = createColorTextWatcher(dialogColorPickerView);
|
|
||||||
editText.addTextChangedListener(colorTextWatcher);
|
|
||||||
inputLayout.addView(editText);
|
|
||||||
|
|
||||||
// Add a dummy view to take up remaining horizontal space,
|
|
||||||
// otherwise it will show an oversize underlined text view.
|
|
||||||
View paddingView = new View(context);
|
|
||||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
|
||||||
0,
|
|
||||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
||||||
1f
|
|
||||||
);
|
|
||||||
paddingView.setLayoutParams(params);
|
|
||||||
inputLayout.addView(paddingView);
|
|
||||||
|
|
||||||
layout.addView(inputLayout);
|
|
||||||
|
|
||||||
// Set up color picker listener with debouncing.
|
|
||||||
// Add listener last to prevent callbacks from set calls above.
|
|
||||||
dialogColorPickerView.setOnColorChangedListener(color -> {
|
|
||||||
// Check if it actually changed, since this callback
|
|
||||||
// can be caused by updates in afterTextChanged().
|
|
||||||
if (currentColor == color) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String updatedColorString = getColorString(color);
|
|
||||||
Logger.printDebug(() -> "onColorChanged: " + updatedColorString);
|
|
||||||
currentColor = color;
|
|
||||||
editText.setText(updatedColorString);
|
|
||||||
editText.setSelection(updatedColorString.length());
|
|
||||||
|
|
||||||
updateColorPreview();
|
|
||||||
updateWidgetColorDot();
|
|
||||||
});
|
|
||||||
|
|
||||||
return layout;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the color preview TextView with a colored dot.
|
* Updates the color preview TextView with a colored dot.
|
||||||
*/
|
*/
|
||||||
@@ -360,65 +279,153 @@ public class ColorPickerPreference extends EditTextPreference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepares the dialog builder with a custom view and reset button.
|
* Creates a Dialog with a color preview and EditText for hex color input.
|
||||||
*
|
|
||||||
* @param builder The AlertDialog.Builder to configure.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
|
||||||
Utils.setEditTextDialogTheme(builder);
|
|
||||||
LinearLayout dialogLayout = createDialogLayout(builder.getContext());
|
|
||||||
builder.setView(dialogLayout);
|
|
||||||
final int originalColor = currentColor;
|
|
||||||
|
|
||||||
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
|
|
||||||
|
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
||||||
try {
|
|
||||||
String colorString = getEditText().getText().toString();
|
|
||||||
|
|
||||||
if (colorString.length() != COLOR_STRING_LENGTH) {
|
|
||||||
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
|
||||||
setText(getColorString(originalColor));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setText(colorString);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// Should never happen due to a bad color string,
|
|
||||||
// since the text is validated and fixed while the user types.
|
|
||||||
Logger.printException(() -> "setPositiveButton failure", ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
|
||||||
try {
|
|
||||||
// Restore the original color.
|
|
||||||
setText(getColorString(originalColor));
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "setNegativeButton failure", ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void showDialog(Bundle state) {
|
protected void showDialog(Bundle state) {
|
||||||
super.showDialog(state);
|
Context context = getContext();
|
||||||
|
|
||||||
AlertDialog dialog = (AlertDialog) getDialog();
|
// Inflate color picker view.
|
||||||
dialog.setCanceledOnTouchOutside(false);
|
View colorPicker = LayoutInflater.from(context).inflate(
|
||||||
|
getResourceIdentifier("revanced_color_picker", "layout"), null);
|
||||||
|
dialogColorPickerView = colorPicker.findViewById(
|
||||||
|
getResourceIdentifier("revanced_color_picker_view", "id"));
|
||||||
|
dialogColorPickerView.setColor(currentColor);
|
||||||
|
|
||||||
// Do not close dialog when reset is pressed.
|
// Horizontal layout for preview and EditText.
|
||||||
Button button = dialog.getButton(AlertDialog.BUTTON_NEUTRAL);
|
LinearLayout inputLayout = new LinearLayout(context);
|
||||||
button.setOnClickListener(view -> {
|
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
try {
|
|
||||||
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
|
dialogColorPreview = new TextView(context);
|
||||||
// Setting view color causes listener callback into this class.
|
LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(
|
||||||
dialogColorPickerView.setColor(defaultColor);
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
} catch (Exception ex) {
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
Logger.printException(() -> "setOnClickListener failure", ex);
|
);
|
||||||
|
previewParams.setMargins(dipToPixels(15), 0, dipToPixels(10), 0); // text dot has its own indents so 15, instead 16.
|
||||||
|
dialogColorPreview.setLayoutParams(previewParams);
|
||||||
|
inputLayout.addView(dialogColorPreview);
|
||||||
|
updateColorPreview();
|
||||||
|
|
||||||
|
EditText editText = getEditText();
|
||||||
|
ViewParent parent = editText.getParent();
|
||||||
|
if (parent instanceof ViewGroup parentViewGroup) {
|
||||||
|
parentViewGroup.removeView(editText);
|
||||||
|
}
|
||||||
|
editText.setLayoutParams(new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
));
|
||||||
|
String currentColorString = getColorString(currentColor);
|
||||||
|
editText.setText(currentColorString);
|
||||||
|
editText.setSelection(currentColorString.length());
|
||||||
|
editText.setTypeface(Typeface.MONOSPACE);
|
||||||
|
colorTextWatcher = createColorTextWatcher(dialogColorPickerView);
|
||||||
|
editText.addTextChangedListener(colorTextWatcher);
|
||||||
|
inputLayout.addView(editText);
|
||||||
|
|
||||||
|
// Add a dummy view to take up remaining horizontal space,
|
||||||
|
// otherwise it will show an oversize underlined text view.
|
||||||
|
View paddingView = new View(context);
|
||||||
|
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||||
|
0,
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
1f
|
||||||
|
);
|
||||||
|
paddingView.setLayoutParams(params);
|
||||||
|
inputLayout.addView(paddingView);
|
||||||
|
|
||||||
|
// Create content container for color picker and input layout.
|
||||||
|
LinearLayout contentContainer = new LinearLayout(context);
|
||||||
|
contentContainer.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
contentContainer.addView(colorPicker);
|
||||||
|
contentContainer.addView(inputLayout);
|
||||||
|
|
||||||
|
// Create ScrollView to wrap the content container.
|
||||||
|
ScrollView contentScrollView = new ScrollView(context);
|
||||||
|
contentScrollView.setVerticalScrollBarEnabled(false); // Disable vertical scrollbar.
|
||||||
|
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); // Disable overscroll effect.
|
||||||
|
LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
0,
|
||||||
|
1.0f
|
||||||
|
);
|
||||||
|
contentScrollView.setLayoutParams(scrollViewParams);
|
||||||
|
contentScrollView.addView(contentContainer);
|
||||||
|
|
||||||
|
// Create custom dialog.
|
||||||
|
final int originalColor = currentColor & 0x00FFFFFF;
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
getTitle() != null ? getTitle().toString() : str("revanced_settings_color_picker_title"), // Title.
|
||||||
|
null, // No message.
|
||||||
|
null, // No EditText.
|
||||||
|
null, // OK button text.
|
||||||
|
() -> {
|
||||||
|
// OK button action.
|
||||||
|
try {
|
||||||
|
String colorString = editText.getText().toString();
|
||||||
|
if (colorString.length() != COLOR_STRING_LENGTH) {
|
||||||
|
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
||||||
|
setText(getColorString(originalColor));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setText(colorString);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
// Should never happen due to a bad color string,
|
||||||
|
// since the text is validated and fixed while the user types.
|
||||||
|
Logger.printException(() -> "OK button failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {
|
||||||
|
// Cancel button action.
|
||||||
|
try {
|
||||||
|
// Restore the original color.
|
||||||
|
setText(getColorString(originalColor));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Cancel button failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
str("revanced_settings_reset_color"), // Neutral button text.
|
||||||
|
() -> {
|
||||||
|
// Neutral button action.
|
||||||
|
try {
|
||||||
|
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
|
||||||
|
// Setting view color causes listener callback into this class.
|
||||||
|
dialogColorPickerView.setColor(defaultColor);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Reset button failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false // Do not dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the ScrollView to the dialog's main layout.
|
||||||
|
LinearLayout dialogMainLayout = dialogPair.second;
|
||||||
|
dialogMainLayout.addView(contentScrollView, dialogMainLayout.getChildCount() - 1);
|
||||||
|
|
||||||
|
// Set up color picker listener with debouncing.
|
||||||
|
// Add listener last to prevent callbacks from set calls above.
|
||||||
|
dialogColorPickerView.setOnColorChangedListener(color -> {
|
||||||
|
// Check if it actually changed, since this callback
|
||||||
|
// can be caused by updates in afterTextChanged().
|
||||||
|
if (currentColor == color) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String updatedColorString = getColorString(color);
|
||||||
|
Logger.printDebug(() -> "onColorChanged: " + updatedColorString);
|
||||||
|
currentColor = color;
|
||||||
|
editText.setText(updatedColorString);
|
||||||
|
editText.setSelection(updatedColorString.length());
|
||||||
|
|
||||||
|
updateColorPreview();
|
||||||
|
updateWidgetColorDot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Configure and show the dialog.
|
||||||
|
Dialog dialog = dialogPair.first;
|
||||||
|
dialog.setCanceledOnTouchOutside(false);
|
||||||
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ import app.revanced.extension.shared.Utils;
|
|||||||
* <p>
|
* <p>
|
||||||
* This view displays two main components for color selection:
|
* This view displays two main components for color selection:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li><b>Hue Bar:</b> A vertical bar on the right that allows the user to select the hue component of the color.
|
* <li><b>Hue Bar:</b> A horizontal bar at the bottom that allows the user to select the hue component of the color.
|
||||||
* <li><b>Saturation-Value Selector:</b> A rectangular area that allows the user to select the saturation and value (brightness)
|
* <li><b>Saturation-Value Selector:</b> A rectangular area above the hue bar that allows the user to select the saturation and value (brightness)
|
||||||
* components of the color based on the selected hue.
|
* components of the color based on the selected hue.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
@@ -63,12 +63,12 @@ public class ColorPickerView extends View {
|
|||||||
|
|
||||||
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24);
|
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24);
|
||||||
private static final float VIEW_PADDING = dipToPixels(16);
|
private static final float VIEW_PADDING = dipToPixels(16);
|
||||||
private static final float HUE_BAR_WIDTH = dipToPixels(12);
|
private static final float HUE_BAR_HEIGHT = dipToPixels(12);
|
||||||
private static final float HUE_CORNER_RADIUS = dipToPixels(6);
|
private static final float HUE_CORNER_RADIUS = dipToPixels(6);
|
||||||
private static final float SELECTOR_RADIUS = dipToPixels(12);
|
private static final float SELECTOR_RADIUS = dipToPixels(12);
|
||||||
private static final float SELECTOR_STROKE_WIDTH = 8;
|
private static final float SELECTOR_STROKE_WIDTH = 8;
|
||||||
/**
|
/**
|
||||||
* Hue fill radius. Use slightly smaller radius for the selector handle fill,
|
* Hue fill radius. Use slightly smaller radius for the selector handle fill,
|
||||||
* otherwise the anti-aliasing causes the fill color to bleed past the selector outline.
|
* otherwise the anti-aliasing causes the fill color to bleed past the selector outline.
|
||||||
*/
|
*/
|
||||||
private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2;
|
private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2;
|
||||||
@@ -144,17 +144,17 @@ public class ColorPickerView extends View {
|
|||||||
final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8
|
final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8
|
||||||
|
|
||||||
final int minWidth = Utils.dipToPixels(250);
|
final int minWidth = Utils.dipToPixels(250);
|
||||||
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO);
|
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS);
|
||||||
|
|
||||||
int width = resolveSize(minWidth, widthMeasureSpec);
|
int width = resolveSize(minWidth, widthMeasureSpec);
|
||||||
int height = resolveSize(minHeight, heightMeasureSpec);
|
int height = resolveSize(minHeight, heightMeasureSpec);
|
||||||
|
|
||||||
// Ensure minimum dimensions for usability
|
// Ensure minimum dimensions for usability.
|
||||||
width = Math.max(width, minWidth);
|
width = Math.max(width, minWidth);
|
||||||
height = Math.max(height, minHeight);
|
height = Math.max(height, minHeight);
|
||||||
|
|
||||||
// Adjust height to maintain desired aspect ratio if possible
|
// Adjust height to maintain desired aspect ratio if possible.
|
||||||
final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO);
|
final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS);
|
||||||
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
|
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
|
||||||
height = desiredHeight;
|
height = desiredHeight;
|
||||||
}
|
}
|
||||||
@@ -171,22 +171,22 @@ public class ColorPickerView extends View {
|
|||||||
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
|
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
|
||||||
super.onSizeChanged(width, height, oldWidth, oldHeight);
|
super.onSizeChanged(width, height, oldWidth, oldHeight);
|
||||||
|
|
||||||
// Calculate bounds with hue bar on the right
|
// Calculate bounds with hue bar at the bottom.
|
||||||
final float effectiveWidth = width - (2 * VIEW_PADDING);
|
final float effectiveWidth = width - (2 * VIEW_PADDING);
|
||||||
final float selectorWidth = effectiveWidth - HUE_BAR_WIDTH - MARGIN_BETWEEN_AREAS;
|
final float effectiveHeight = height - (2 * VIEW_PADDING) - HUE_BAR_HEIGHT - MARGIN_BETWEEN_AREAS;
|
||||||
|
|
||||||
// Adjust rectangles to account for padding and density-independent dimensions
|
// Adjust rectangles to account for padding and density-independent dimensions.
|
||||||
saturationValueRect.set(
|
saturationValueRect.set(
|
||||||
VIEW_PADDING,
|
VIEW_PADDING,
|
||||||
VIEW_PADDING,
|
VIEW_PADDING,
|
||||||
VIEW_PADDING + selectorWidth,
|
VIEW_PADDING + effectiveWidth,
|
||||||
height - VIEW_PADDING
|
VIEW_PADDING + effectiveHeight
|
||||||
);
|
);
|
||||||
|
|
||||||
hueRect.set(
|
hueRect.set(
|
||||||
width - VIEW_PADDING - HUE_BAR_WIDTH,
|
|
||||||
VIEW_PADDING,
|
VIEW_PADDING,
|
||||||
width - VIEW_PADDING,
|
height - VIEW_PADDING - HUE_BAR_HEIGHT,
|
||||||
|
VIEW_PADDING + effectiveWidth,
|
||||||
height - VIEW_PADDING
|
height - VIEW_PADDING
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ public class ColorPickerView extends View {
|
|||||||
private void updateHueShader() {
|
private void updateHueShader() {
|
||||||
LinearGradient hueShader = new LinearGradient(
|
LinearGradient hueShader = new LinearGradient(
|
||||||
hueRect.left, hueRect.top,
|
hueRect.left, hueRect.top,
|
||||||
hueRect.left, hueRect.bottom,
|
hueRect.right, hueRect.top,
|
||||||
HUE_COLORS,
|
HUE_COLORS,
|
||||||
null,
|
null,
|
||||||
Shader.TileMode.CLAMP
|
Shader.TileMode.CLAMP
|
||||||
@@ -263,8 +263,8 @@ public class ColorPickerView extends View {
|
|||||||
// Draw the hue bar.
|
// Draw the hue bar.
|
||||||
canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint);
|
canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint);
|
||||||
|
|
||||||
final float hueSelectorX = hueRect.centerX();
|
final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width();
|
||||||
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height();
|
final float hueSelectorY = hueRect.centerY();
|
||||||
|
|
||||||
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
|
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
|
||||||
final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
|
final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
|
||||||
@@ -316,17 +316,17 @@ public class ColorPickerView extends View {
|
|||||||
|
|
||||||
// Define touch expansion for the hue bar.
|
// Define touch expansion for the hue bar.
|
||||||
RectF expandedHueRect = new RectF(
|
RectF expandedHueRect = new RectF(
|
||||||
hueRect.left - TOUCH_EXPANSION,
|
hueRect.left,
|
||||||
hueRect.top,
|
hueRect.top - TOUCH_EXPANSION,
|
||||||
hueRect.right + TOUCH_EXPANSION,
|
hueRect.right,
|
||||||
hueRect.bottom
|
hueRect.bottom + TOUCH_EXPANSION
|
||||||
);
|
);
|
||||||
|
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case MotionEvent.ACTION_DOWN:
|
case MotionEvent.ACTION_DOWN:
|
||||||
// Calculate current handle positions.
|
// Calculate current handle positions.
|
||||||
final float hueSelectorX = hueRect.centerX();
|
final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width();
|
||||||
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height();
|
final float hueSelectorY = hueRect.centerY();
|
||||||
|
|
||||||
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
|
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
|
||||||
final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
|
final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
|
||||||
@@ -348,14 +348,14 @@ public class ColorPickerView extends View {
|
|||||||
// Check if the touch started on a handle or within the expanded hue bar area.
|
// Check if the touch started on a handle or within the expanded hue bar area.
|
||||||
if (hueHitRect.contains(x, y)) {
|
if (hueHitRect.contains(x, y)) {
|
||||||
isDraggingHue = true;
|
isDraggingHue = true;
|
||||||
updateHueFromTouch(y);
|
updateHueFromTouch(x);
|
||||||
} else if (satValHitRect.contains(x, y)) {
|
} else if (satValHitRect.contains(x, y)) {
|
||||||
isDraggingSaturation = true;
|
isDraggingSaturation = true;
|
||||||
updateSaturationValueFromTouch(x, y);
|
updateSaturationValueFromTouch(x, y);
|
||||||
} else if (expandedHueRect.contains(x, y)) {
|
} else if (expandedHueRect.contains(x, y)) {
|
||||||
// Handle touch within the expanded hue bar area.
|
// Handle touch within the expanded hue bar area.
|
||||||
isDraggingHue = true;
|
isDraggingHue = true;
|
||||||
updateHueFromTouch(y);
|
updateHueFromTouch(x);
|
||||||
} else if (saturationValueRect.contains(x, y)) {
|
} else if (saturationValueRect.contains(x, y)) {
|
||||||
isDraggingSaturation = true;
|
isDraggingSaturation = true;
|
||||||
updateSaturationValueFromTouch(x, y);
|
updateSaturationValueFromTouch(x, y);
|
||||||
@@ -365,7 +365,7 @@ public class ColorPickerView extends View {
|
|||||||
case MotionEvent.ACTION_MOVE:
|
case MotionEvent.ACTION_MOVE:
|
||||||
// Continue updating values even if touch moves outside the view.
|
// Continue updating values even if touch moves outside the view.
|
||||||
if (isDraggingHue) {
|
if (isDraggingHue) {
|
||||||
updateHueFromTouch(y);
|
updateHueFromTouch(x);
|
||||||
} else if (isDraggingSaturation) {
|
} else if (isDraggingSaturation) {
|
||||||
updateSaturationValueFromTouch(x, y);
|
updateSaturationValueFromTouch(x, y);
|
||||||
}
|
}
|
||||||
@@ -387,12 +387,12 @@ public class ColorPickerView extends View {
|
|||||||
/**
|
/**
|
||||||
* Updates the hue value based on touch position, clamping to valid range.
|
* Updates the hue value based on touch position, clamping to valid range.
|
||||||
*
|
*
|
||||||
* @param y The y-coordinate of the touch position.
|
* @param x The x-coordinate of the touch position.
|
||||||
*/
|
*/
|
||||||
private void updateHueFromTouch(float y) {
|
private void updateHueFromTouch(float x) {
|
||||||
// Clamp y to the hue rectangle bounds.
|
// Clamp x to the hue rectangle bounds.
|
||||||
final float clampedY = Utils.clamp(y, hueRect.top, hueRect.bottom);
|
final float clampedX = Utils.clamp(x, hueRect.left, hueRect.right);
|
||||||
final float updatedHue = ((clampedY - hueRect.top) / hueRect.height()) * 360f;
|
final float updatedHue = ((clampedX - hueRect.left) / hueRect.width()) * 360f;
|
||||||
if (hue == updatedHue) {
|
if (hue == updatedHue) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.ListPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.*;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom ListPreference that uses a styled custom dialog with a custom checkmark indicator.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
|
public class CustomDialogListPreference extends ListPreference {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom ArrayAdapter to handle checkmark visibility.
|
||||||
|
*/
|
||||||
|
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
|
||||||
|
private static class SubViewDataContainer {
|
||||||
|
ImageView checkIcon;
|
||||||
|
View placeholder;
|
||||||
|
TextView itemText;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int layoutResourceId;
|
||||||
|
final CharSequence[] entryValues;
|
||||||
|
String selectedValue;
|
||||||
|
|
||||||
|
public ListPreferenceArrayAdapter(Context context, int resource, CharSequence[] entries,
|
||||||
|
CharSequence[] entryValues, String selectedValue) {
|
||||||
|
super(context, resource, entries);
|
||||||
|
this.layoutResourceId = resource;
|
||||||
|
this.entryValues = entryValues;
|
||||||
|
this.selectedValue = selectedValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
|
||||||
|
View view = convertView;
|
||||||
|
SubViewDataContainer holder;
|
||||||
|
|
||||||
|
if (view == null) {
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
view = inflater.inflate(layoutResourceId, parent, false);
|
||||||
|
holder = new SubViewDataContainer();
|
||||||
|
holder.checkIcon = view.findViewById(Utils.getResourceIdentifier(
|
||||||
|
"revanced_check_icon", "id"));
|
||||||
|
holder.placeholder = view.findViewById(Utils.getResourceIdentifier(
|
||||||
|
"revanced_check_icon_placeholder", "id"));
|
||||||
|
holder.itemText = view.findViewById(Utils.getResourceIdentifier(
|
||||||
|
"revanced_item_text", "id"));
|
||||||
|
view.setTag(holder);
|
||||||
|
} else {
|
||||||
|
holder = (SubViewDataContainer) view.getTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set text.
|
||||||
|
holder.itemText.setText(getItem(position));
|
||||||
|
holder.itemText.setTextColor(Utils.getAppForegroundColor());
|
||||||
|
|
||||||
|
// Show or hide checkmark and placeholder.
|
||||||
|
String currentValue = entryValues[position].toString();
|
||||||
|
boolean isSelected = currentValue.equals(selectedValue);
|
||||||
|
holder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE);
|
||||||
|
holder.checkIcon.setColorFilter(Utils.getAppForegroundColor());
|
||||||
|
holder.placeholder.setVisibility(isSelected ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelectedValue(String value) {
|
||||||
|
this.selectedValue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomDialogListPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CustomDialogListPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void showDialog(Bundle state) {
|
||||||
|
Context context = getContext();
|
||||||
|
|
||||||
|
// Create ListView.
|
||||||
|
ListView listView = new ListView(context);
|
||||||
|
listView.setId(android.R.id.list);
|
||||||
|
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
|
||||||
|
|
||||||
|
// Create custom adapter for the ListView.
|
||||||
|
ListPreferenceArrayAdapter adapter = new ListPreferenceArrayAdapter(
|
||||||
|
context,
|
||||||
|
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
|
||||||
|
getEntries(),
|
||||||
|
getEntryValues(),
|
||||||
|
getValue()
|
||||||
|
);
|
||||||
|
listView.setAdapter(adapter);
|
||||||
|
|
||||||
|
// Set checked item.
|
||||||
|
String currentValue = getValue();
|
||||||
|
if (currentValue != null) {
|
||||||
|
CharSequence[] entryValues = getEntryValues();
|
||||||
|
for (int i = 0, length = entryValues.length; i < length; i++) {
|
||||||
|
if (currentValue.equals(entryValues[i].toString())) {
|
||||||
|
listView.setItemChecked(i, true);
|
||||||
|
listView.setSelection(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the custom dialog without OK button.
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
getTitle() != null ? getTitle().toString() : "",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null, // No OK button text.
|
||||||
|
null, // No OK button action.
|
||||||
|
() -> {}, // Cancel button action (just dismiss).
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the ListView to the main layout.
|
||||||
|
LinearLayout mainLayout = dialogPair.second;
|
||||||
|
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
0,
|
||||||
|
1.0f
|
||||||
|
);
|
||||||
|
mainLayout.addView(listView, mainLayout.getChildCount() - 1, listViewParams);
|
||||||
|
|
||||||
|
// Handle item click to select value and dismiss dialog.
|
||||||
|
listView.setOnItemClickListener((parent, view, position, id) -> {
|
||||||
|
String selectedValue = getEntryValues()[position].toString();
|
||||||
|
if (callChangeListener(selectedValue)) {
|
||||||
|
setValue(selectedValue);
|
||||||
|
adapter.setSelectedValue(selectedValue);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
dialogPair.first.dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,30 @@
|
|||||||
package app.revanced.extension.shared.settings.preference;
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Pair;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
||||||
@@ -54,7 +65,8 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
|||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
try {
|
try {
|
||||||
// Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened.
|
// Must set text before showing dialog,
|
||||||
|
// otherwise text is non-selectable if this preference is later reopened.
|
||||||
existingSettings = Setting.exportToJson(getContext());
|
existingSettings = Setting.exportToJson(getContext());
|
||||||
getEditText().setText(existingSettings);
|
getEditText().setText(existingSettings);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -64,18 +76,32 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
protected void showDialog(Bundle state) {
|
||||||
try {
|
try {
|
||||||
Utils.setEditTextDialogTheme(builder);
|
Context context = getContext();
|
||||||
|
EditText editText = getEditText();
|
||||||
|
|
||||||
// Show the user the settings in JSON format.
|
// Create a custom dialog with the EditText.
|
||||||
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> {
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
Utils.setClipboard(getEditText().getText());
|
context,
|
||||||
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> {
|
str("revanced_pref_import_export_title"), // Title.
|
||||||
importSettings(builder.getContext(), getEditText().getText().toString());
|
null, // No message (EditText replaces it).
|
||||||
});
|
editText, // Pass the EditText.
|
||||||
|
str("revanced_settings_import"), // OK button text.
|
||||||
|
() -> importSettings(context, editText.getText().toString()), // OK button action.
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
str("revanced_settings_import_copy"), // Neutral button (Copy) text.
|
||||||
|
() -> {
|
||||||
|
// Neutral button (Copy) action. Show the user the settings in JSON format.
|
||||||
|
Utils.setClipboard(editText.getText());
|
||||||
|
},
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "onPrepareDialogBuilder failure", ex);
|
Logger.printException(() -> "showDialog failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +114,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
|||||||
|
|
||||||
final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings);
|
final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings);
|
||||||
if (rebootNeeded) {
|
if (rebootNeeded) {
|
||||||
AbstractPreferenceFragment.showRestartDialog(getContext());
|
AbstractPreferenceFragment.showRestartDialog(context);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "importSettings failure", ex);
|
Logger.printException(() -> "importSettings failure", ex);
|
||||||
@@ -96,5 +122,4 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
|||||||
AbstractPreferenceFragment.settingImportInProgress = false;
|
AbstractPreferenceFragment.settingImportInProgress = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.revanced.extension.shared.settings.preference;
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
import static app.revanced.extension.shared.requests.Route.Method.GET;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@@ -8,16 +9,19 @@ import android.app.Dialog;
|
|||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Color;
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.view.View;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -48,28 +52,6 @@ public class ReVancedAboutPreference extends Preference {
|
|||||||
return text.replace("-", "‑"); // #8209 = non breaking hyphen.
|
return text.replace("-", "‑"); // #8209 = non breaking hyphen.
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getColorHexString(int color) {
|
|
||||||
return String.format("#%06X", (0x00FFFFFF & color));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isDarkModeEnabled() {
|
|
||||||
return Utils.isDarkModeEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclasses can override this and provide a themed color.
|
|
||||||
*/
|
|
||||||
protected int getLightColor() {
|
|
||||||
return Color.WHITE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subclasses can override this and provide a themed color.
|
|
||||||
*/
|
|
||||||
protected int getDarkColor() {
|
|
||||||
return Color.BLACK;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apps that do not support bundling resources must override this.
|
* Apps that do not support bundling resources must override this.
|
||||||
*
|
*
|
||||||
@@ -86,9 +68,8 @@ public class ReVancedAboutPreference extends Preference {
|
|||||||
builder.append("<html>");
|
builder.append("<html>");
|
||||||
builder.append("<body style=\"text-align: center; padding: 10px;\">");
|
builder.append("<body style=\"text-align: center; padding: 10px;\">");
|
||||||
|
|
||||||
final boolean isDarkMode = isDarkModeEnabled();
|
String foregroundColorHex = Utils.getColorHexString(Utils.getAppForegroundColor());
|
||||||
String backgroundColorHex = getColorHexString(isDarkMode ? getDarkColor() : getLightColor());
|
String backgroundColorHex = Utils.getColorHexString(Utils.getDialogBackgroundColor());
|
||||||
String foregroundColorHex = getColorHexString(isDarkMode ? getLightColor() : getDarkColor());
|
|
||||||
// Apply light/dark mode colors.
|
// Apply light/dark mode colors.
|
||||||
builder.append(String.format(
|
builder.append(String.format(
|
||||||
"<style> body { background-color: %s; color: %s; } a { color: %s; } </style>",
|
"<style> body { background-color: %s; color: %s; } a { color: %s; } </style>",
|
||||||
@@ -220,14 +201,38 @@ class WebViewDialog extends Dialog {
|
|||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
|
||||||
|
|
||||||
|
// Create main layout.
|
||||||
|
LinearLayout mainLayout = new LinearLayout(getContext());
|
||||||
|
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
final int padding = dipToPixels(10);
|
||||||
|
mainLayout.setPadding(padding, padding, padding, padding);
|
||||||
|
// Set rounded rectangle background.
|
||||||
|
ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape(
|
||||||
|
Utils.createCornerRadii(28), null, null));
|
||||||
|
mainBackground.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||||
|
mainLayout.setBackground(mainBackground);
|
||||||
|
|
||||||
|
// Create WebView.
|
||||||
WebView webView = new WebView(getContext());
|
WebView webView = new WebView(getContext());
|
||||||
|
webView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
|
||||||
|
webView.setOverScrollMode(View.OVER_SCROLL_NEVER);
|
||||||
webView.getSettings().setJavaScriptEnabled(true);
|
webView.getSettings().setJavaScriptEnabled(true);
|
||||||
webView.setWebViewClient(new OpenLinksExternallyWebClient());
|
webView.setWebViewClient(new OpenLinksExternallyWebClient());
|
||||||
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null);
|
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null);
|
||||||
|
|
||||||
setContentView(webView);
|
// Add WebView to layout.
|
||||||
|
mainLayout.addView(webView);
|
||||||
|
|
||||||
|
setContentView(mainLayout);
|
||||||
|
|
||||||
|
// Set dialog window attributes
|
||||||
|
Window window = getWindow();
|
||||||
|
if (window != null) {
|
||||||
|
Utils.setDialogWindowParameters(window);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class OpenLinksExternallyWebClient extends WebViewClient {
|
private class OpenLinksExternallyWebClient extends WebViewClient {
|
||||||
@@ -315,7 +320,7 @@ class AboutLinksRoutes {
|
|||||||
// Do not show an exception toast if the server is down
|
// Do not show an exception toast if the server is down
|
||||||
final int responseCode = connection.getResponseCode();
|
final int responseCode = connection.getResponseCode();
|
||||||
if (responseCode != 200) {
|
if (responseCode != 200) {
|
||||||
Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode);
|
Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode);
|
||||||
return NO_CONNECTION_STATIC_LINKS;
|
return NO_CONNECTION_STATIC_LINKS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,28 @@
|
|||||||
package app.revanced.extension.shared.settings.preference;
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.graphics.drawable.LayerDrawable;
|
||||||
|
import android.graphics.drawable.shapes.RectShape;
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.graphics.Paint.Style;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@@ -44,41 +58,61 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||||||
this.setting = setting;
|
this.setting = setting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
|
||||||
super.onPrepareDialogBuilder(builder);
|
|
||||||
Utils.setEditTextDialogTheme(builder);
|
|
||||||
|
|
||||||
if (setting == null) {
|
|
||||||
String key = getKey();
|
|
||||||
if (key != null) {
|
|
||||||
setting = Setting.getSettingFromPath(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (setting != null) {
|
|
||||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void showDialog(Bundle state) {
|
protected void showDialog(Bundle state) {
|
||||||
super.showDialog(state);
|
try {
|
||||||
|
Context context = getContext();
|
||||||
|
EditText editText = getEditText();
|
||||||
|
|
||||||
// Override the button click listener to prevent dismissing the dialog.
|
// Resolve setting if not already set.
|
||||||
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
|
if (setting == null) {
|
||||||
if (button == null) {
|
String key = getKey();
|
||||||
return;
|
if (key != null) {
|
||||||
}
|
setting = Setting.getSettingFromPath(key);
|
||||||
button.setOnClickListener(v -> {
|
}
|
||||||
try {
|
|
||||||
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
|
|
||||||
EditText editText = getEditText();
|
|
||||||
editText.setText(defaultStringValue);
|
|
||||||
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "reset failure", ex);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
// Set initial EditText value to the current persisted value or empty string.
|
||||||
|
String initialValue = getText() != null ? getText() : "";
|
||||||
|
editText.setText(initialValue);
|
||||||
|
editText.setSelection(initialValue.length()); // Move cursor to end.
|
||||||
|
|
||||||
|
// Create custom dialog.
|
||||||
|
String neutralButtonText = (setting != null) ? str("revanced_settings_reset") : null;
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
getTitle() != null ? getTitle().toString() : "", // Title.
|
||||||
|
null, // Message is replaced by EditText.
|
||||||
|
editText, // Pass the EditText.
|
||||||
|
null, // OK button text.
|
||||||
|
() -> {
|
||||||
|
// OK button action. Persist the EditText value when OK is clicked.
|
||||||
|
String newValue = editText.getText().toString();
|
||||||
|
if (callChangeListener(newValue)) {
|
||||||
|
setText(newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
neutralButtonText, // Neutral button text (Reset).
|
||||||
|
() -> {
|
||||||
|
// Neutral button action.
|
||||||
|
if (setting != null) {
|
||||||
|
try {
|
||||||
|
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
|
||||||
|
editText.setText(defaultStringValue);
|
||||||
|
editText.setSelection(defaultStringValue.length()); // Move cursor to end of text.
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "reset failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false // Do not dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "showDialog failure", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package app.revanced.extension.shared.settings.preference;
|
package app.revanced.extension.shared.settings.preference;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.ListPreference;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
@@ -24,12 +23,14 @@ import app.revanced.extension.shared.Utils;
|
|||||||
* it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}.
|
* it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public class SortedListPreference extends ListPreference {
|
public class SortedListPreference extends CustomDialogListPreference {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts the current list entries.
|
* Sorts the current list entries.
|
||||||
*
|
*
|
||||||
* @param firstEntriesToPreserve The number of entries to preserve in their original position.
|
* @param firstEntriesToPreserve The number of entries to preserve in their original position,
|
||||||
|
* or a negative value to not sort and leave entries
|
||||||
|
* as they current are.
|
||||||
*/
|
*/
|
||||||
public void sortEntryAndValues(int firstEntriesToPreserve) {
|
public void sortEntryAndValues(int firstEntriesToPreserve) {
|
||||||
CharSequence[] entries = getEntries();
|
CharSequence[] entries = getEntries();
|
||||||
@@ -44,6 +45,10 @@ public class SortedListPreference extends ListPreference {
|
|||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (firstEntriesToPreserve < 0) {
|
||||||
|
return; // Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve);
|
List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve);
|
||||||
|
|
||||||
// Android does not have a triple class like Kotlin, So instead use a nested pair.
|
// Android does not have a triple class like Kotlin, So instead use a nested pair.
|
||||||
@@ -85,10 +90,6 @@ public class SortedListPreference extends ListPreference {
|
|||||||
super.setEntryValues(sortedEntryValues);
|
super.setEntryValues(sortedEntryValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected int getFirstEntriesToPreserve() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
|
||||||
@@ -112,4 +113,12 @@ public class SortedListPreference extends ListPreference {
|
|||||||
|
|
||||||
sortEntryAndValues(getFirstEntriesToPreserve());
|
sortEntryAndValues(getFirstEntriesToPreserve());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The number of first entries to leave exactly where they are, and do not sort them.
|
||||||
|
* A negative value indicates do not sort any entries.
|
||||||
|
*/
|
||||||
|
protected int getFirstEntriesToPreserve() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
|
plugins {
|
||||||
|
alias(libs.plugins.protobuf)
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:spotify:stub"))
|
compileOnly(project(":extensions:spotify:stub"))
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
|
|
||||||
|
implementation(libs.nanohttpd)
|
||||||
|
implementation(libs.protobuf.javalite)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
@@ -14,3 +21,19 @@ android {
|
|||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protobuf {
|
||||||
|
protoc {
|
||||||
|
artifact = libs.protobuf.protoc.get().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
generateProtoTasks {
|
||||||
|
all().forEach { task ->
|
||||||
|
task.builtins {
|
||||||
|
create("java") {
|
||||||
|
option("lite")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
package app.revanced.extension.spotify.layout.hide.createbutton;
|
package app.revanced.extension.spotify.layout.hide.createbutton;
|
||||||
|
|
||||||
import java.util.List;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
|
||||||
|
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
|
||||||
|
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Utils;
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class HideCreateButtonPatch {
|
public final class HideCreateButtonPatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A list of ids of resources which contain the Create button title.
|
* A list of component filters that match whether a navigation bar item is the Create button.
|
||||||
|
* The main approach used is matching the resource id for the Create button title.
|
||||||
*/
|
*/
|
||||||
private static final List<String> CREATE_BUTTON_TITLE_RES_ID_LIST = List.of(
|
private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
|
||||||
Integer.toString(Utils.getResourceIdentifier("navigationbar_musicappitems_create_title", "string"))
|
new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"),
|
||||||
|
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded,
|
||||||
|
// and thus getting the resource identifier for the Create button title always return 0.
|
||||||
|
// FIXME: Remove this once the above issue is no longer relevant.
|
||||||
|
new StringComponentFilter("spotify:create-menu")
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The old id of the resource which contained the Create button title. Used in older versions of the app.
|
* A component filter for the old id of the resource which contained the Create button title.
|
||||||
|
* Used in older versions of the app.
|
||||||
*/
|
*/
|
||||||
private static final int OLD_CREATE_BUTTON_TITLE_RES_ID =
|
private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
|
||||||
Utils.getResourceIdentifier("bottom_navigation_bar_create_tab_title", "string");
|
new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point. This method is called on every navigation bar item to check whether it is the Create button.
|
* Injection point. This method is called on every navigation bar item to check whether it is the Create button.
|
||||||
@@ -30,12 +39,26 @@ public final class HideCreateButtonPatch {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String stringifiedNavigationBarItem = navigationBarItem.toString();
|
try {
|
||||||
boolean isCreateButton = CREATE_BUTTON_TITLE_RES_ID_LIST.stream()
|
String stringifiedNavigationBarItem = navigationBarItem.toString();
|
||||||
.anyMatch(stringifiedNavigationBarItem::contains);
|
|
||||||
|
|
||||||
if (isCreateButton) {
|
for (ComponentFilter componentFilter : CREATE_BUTTON_COMPONENT_FILTERS) {
|
||||||
return null;
|
if (componentFilter.filterUnavailable()) {
|
||||||
|
Logger.printInfo(() -> "returnNullIfIsCreateButton: Filter " +
|
||||||
|
componentFilter.getFilterRepresentation() + " not available, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stringifiedNavigationBarItem.contains(componentFilter.getFilterValue())) {
|
||||||
|
Logger.printInfo(() -> "Hiding Create button because the navigation bar item " +
|
||||||
|
navigationBarItem + " matched the filter " + componentFilter.getFilterRepresentation());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
// Catch Throwable as calling toString can cause crashes with wrongfully generated code that throws
|
||||||
|
// NoSuchMethod errors.
|
||||||
|
Logger.printException(() -> "returnNullIfIsCreateButton failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
return navigationBarItem;
|
return navigationBarItem;
|
||||||
@@ -46,6 +69,18 @@ public final class HideCreateButtonPatch {
|
|||||||
* Create button.
|
* Create button.
|
||||||
*/
|
*/
|
||||||
public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) {
|
public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) {
|
||||||
return oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_TITLE_RES_ID;
|
if (OLD_CREATE_BUTTON_COMPONENT_FILTER.filterUnavailable()) {
|
||||||
|
Logger.printInfo(() -> "Skipping hiding old Create button because the resource id for " +
|
||||||
|
OLD_CREATE_BUTTON_COMPONENT_FILTER.resourceName + " is not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_COMPONENT_FILTER.getResourceId()) {
|
||||||
|
Logger.printInfo(() -> "Hiding old Create button because the navigation bar item title resource id" +
|
||||||
|
" matched " + OLD_CREATE_BUTTON_COMPONENT_FILTER.getFilterRepresentation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.Constants.*;
|
||||||
|
|
||||||
|
class ClientTokenService {
|
||||||
|
private static final String IOS_CLIENT_ID = "58bd3c95768941ea9eb4350aaa033eb3";
|
||||||
|
private static final String IOS_USER_AGENT;
|
||||||
|
|
||||||
|
static {
|
||||||
|
String clientVersion = getClientVersion();
|
||||||
|
int commitHashIndex = clientVersion.lastIndexOf(".");
|
||||||
|
String version = clientVersion.substring(
|
||||||
|
clientVersion.indexOf("-") + 1,
|
||||||
|
clientVersion.lastIndexOf(".", commitHashIndex - 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
IOS_USER_AGENT = "Spotify/" + version + " iOS/" + getSystemVersion() + " (" + getHardwareMachine() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final ConnectivitySdkData.Builder IOS_CONNECTIVITY_SDK_DATA =
|
||||||
|
ConnectivitySdkData.newBuilder()
|
||||||
|
.setPlatformSpecificData(PlatformSpecificData.newBuilder()
|
||||||
|
.setIos(NativeIOSData.newBuilder()
|
||||||
|
.setHwMachine(getHardwareMachine())
|
||||||
|
.setSystemVersion(getSystemVersion())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final ClientDataRequest.Builder IOS_CLIENT_DATA_REQUEST =
|
||||||
|
ClientDataRequest.newBuilder()
|
||||||
|
.setClientVersion(getClientVersion())
|
||||||
|
.setClientId(IOS_CLIENT_ID);
|
||||||
|
|
||||||
|
private static final ClientTokenRequest.Builder IOS_CLIENT_TOKEN_REQUEST =
|
||||||
|
ClientTokenRequest.newBuilder()
|
||||||
|
.setRequestType(ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST);
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
static ClientTokenRequest newIOSClientTokenRequest(String deviceId) {
|
||||||
|
Logger.printInfo(() -> "Creating new iOS client token request with device ID: " + deviceId);
|
||||||
|
|
||||||
|
return IOS_CLIENT_TOKEN_REQUEST
|
||||||
|
.setClientData(IOS_CLIENT_DATA_REQUEST
|
||||||
|
.setConnectivitySdkData(IOS_CONNECTIVITY_SDK_DATA
|
||||||
|
.setDeviceId(deviceId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static ClientTokenResponse getClientTokenResponse(@NonNull ClientTokenRequest request) {
|
||||||
|
if (request.getRequestType() == ClientTokenRequestType.REQUEST_CLIENT_DATA_REQUEST) {
|
||||||
|
Logger.printInfo(() -> "Requesting iOS client token");
|
||||||
|
String deviceId = request.getClientData().getConnectivitySdkData().getDeviceId();
|
||||||
|
request = newIOSClientTokenRequest(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientTokenResponse response;
|
||||||
|
try {
|
||||||
|
response = requestClientToken(request);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to handle request", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static ClientTokenResponse requestClientToken(@NonNull ClientTokenRequest request) throws IOException {
|
||||||
|
HttpURLConnection urlConnection = (HttpURLConnection) new URL(CLIENT_TOKEN_API_URL).openConnection();
|
||||||
|
urlConnection.setRequestMethod("POST");
|
||||||
|
urlConnection.setDoOutput(true);
|
||||||
|
urlConnection.setRequestProperty("Content-Type", "application/x-protobuf");
|
||||||
|
urlConnection.setRequestProperty("Accept", "application/x-protobuf");
|
||||||
|
urlConnection.setRequestProperty("User-Agent", IOS_USER_AGENT);
|
||||||
|
|
||||||
|
byte[] requestArray = request.toByteArray();
|
||||||
|
urlConnection.setFixedLengthStreamingMode(requestArray.length);
|
||||||
|
urlConnection.getOutputStream().write(requestArray);
|
||||||
|
|
||||||
|
try (InputStream inputStream = urlConnection.getInputStream()) {
|
||||||
|
return ClientTokenResponse.parseFrom(inputStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static ClientTokenResponse serveClientTokenRequest(@NonNull InputStream inputStream) {
|
||||||
|
ClientTokenRequest request;
|
||||||
|
try {
|
||||||
|
request = ClientTokenRequest.parseFrom(inputStream);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to parse request from input stream", ex);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Logger.printInfo(() -> "Request of type: " + request.getRequestType());
|
||||||
|
|
||||||
|
ClientTokenResponse response = getClientTokenResponse(request);
|
||||||
|
if (response != null) Logger.printInfo(() -> "Response of type: " + response.getResponseType());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
class Constants {
|
||||||
|
static final String CLIENT_TOKEN_API_PATH = "/v1/clienttoken";
|
||||||
|
static final String CLIENT_TOKEN_API_URL = "https://clienttoken.spotify.com" + CLIENT_TOKEN_API_PATH;
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getClientVersion() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getSystemVersion() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified by a patch. Do not touch.
|
||||||
|
@NonNull
|
||||||
|
static String getHardwareMachine() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.spotify.misc.fix.clienttoken.data.v0.ClienttokenHttp.ClientTokenResponse;
|
||||||
|
import com.google.protobuf.MessageLite;
|
||||||
|
import fi.iki.elonen.NanoHTTPD;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.FilterInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.ClientTokenService.serveClientTokenRequest;
|
||||||
|
import static app.revanced.extension.spotify.misc.fix.Constants.CLIENT_TOKEN_API_PATH;
|
||||||
|
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
|
||||||
|
|
||||||
|
class RequestListener extends NanoHTTPD {
|
||||||
|
RequestListener(int port) {
|
||||||
|
super(port);
|
||||||
|
|
||||||
|
try {
|
||||||
|
start();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
Logger.printException(() -> "Failed to start request listener on port " + port, ex);
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Response serve(@NonNull IHTTPSession session) {
|
||||||
|
String uri = session.getUri();
|
||||||
|
if (!uri.equals(CLIENT_TOKEN_API_PATH)) return INTERNAL_ERROR_RESPONSE;
|
||||||
|
|
||||||
|
Logger.printInfo(() -> "Serving request for URI: " + uri);
|
||||||
|
|
||||||
|
ClientTokenResponse response = serveClientTokenRequest(getInputStream(session));
|
||||||
|
if (response != null) return newResponse(Response.Status.OK, response);
|
||||||
|
|
||||||
|
Logger.printException(() -> "Failed to serve client token request");
|
||||||
|
return INTERNAL_ERROR_RESPONSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static InputStream newLimitedInputStream(InputStream inputStream, long contentLength) {
|
||||||
|
return new FilterInputStream(inputStream) {
|
||||||
|
private long remaining = contentLength;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (remaining <= 0) return -1;
|
||||||
|
int result = super.read();
|
||||||
|
if (result != -1) remaining--;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(byte[] b, int off, int len) throws IOException {
|
||||||
|
if (remaining <= 0) return -1;
|
||||||
|
len = (int) Math.min(len, remaining);
|
||||||
|
int result = super.read(b, off, len);
|
||||||
|
if (result != -1) remaining -= result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static InputStream getInputStream(@NonNull IHTTPSession session) {
|
||||||
|
long requestContentLength = Long.parseLong(Objects.requireNonNull(session.getHeaders().get("content-length")));
|
||||||
|
return newLimitedInputStream(session.getInputStream(), requestContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Response INTERNAL_ERROR_RESPONSE = newResponse(INTERNAL_ERROR);
|
||||||
|
|
||||||
|
@SuppressWarnings("SameParameterValue")
|
||||||
|
@NonNull
|
||||||
|
private static Response newResponse(Response.Status status) {
|
||||||
|
return newResponse(status, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static Response newResponse(Response.IStatus status, MessageLite messageLite) {
|
||||||
|
if (messageLite == null) {
|
||||||
|
return newFixedLengthResponse(status, "application/x-protobuf", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] messageBytes = messageLite.toByteArray();
|
||||||
|
InputStream stream = new ByteArrayInputStream(messageBytes);
|
||||||
|
return newFixedLengthResponse(status, "application/x-protobuf", stream, messageBytes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package app.revanced.extension.spotify.misc.fix;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class SpoofClientPatch {
|
||||||
|
private static RequestListener listener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Launch requests listener server.
|
||||||
|
*/
|
||||||
|
public synchronized static void launchListener(int port) {
|
||||||
|
if (listener != null) {
|
||||||
|
Logger.printInfo(() -> "Listener already running on port " + port);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Logger.printInfo(() -> "Launching listener on port " + port);
|
||||||
|
listener = new RequestListener(port);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "launchListener failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,10 +33,11 @@ public final class SanitizeSharingLinksPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.build().toString();
|
String sanitizedUrl = builder.build().toString();
|
||||||
|
Logger.printInfo(() -> "Sanitized url " + url + " to " + sanitizedUrl);
|
||||||
|
return sanitizedUrl;
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "sanitizeUrl failure", ex);
|
Logger.printException(() -> "sanitizeUrl failure with " + url, ex);
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package app.revanced.extension.spotify.shared;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
|
||||||
|
public final class ComponentFilters {
|
||||||
|
|
||||||
|
public interface ComponentFilter {
|
||||||
|
@NonNull
|
||||||
|
String getFilterValue();
|
||||||
|
String getFilterRepresentation();
|
||||||
|
default boolean filterUnavailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class ResourceIdComponentFilter implements ComponentFilter {
|
||||||
|
|
||||||
|
public final String resourceName;
|
||||||
|
public final String resourceType;
|
||||||
|
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
|
||||||
|
// 0 is returned when a resource has not been found.
|
||||||
|
private int resourceId = -1;
|
||||||
|
@Nullable
|
||||||
|
private String stringfiedResourceId;
|
||||||
|
|
||||||
|
public ResourceIdComponentFilter(String resourceName, String resourceType) {
|
||||||
|
this.resourceName = resourceName;
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResourceId() {
|
||||||
|
if (resourceId == -1) {
|
||||||
|
resourceId = Utils.getResourceIdentifier(resourceName, resourceType);
|
||||||
|
}
|
||||||
|
return resourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String getFilterValue() {
|
||||||
|
if (stringfiedResourceId == null) {
|
||||||
|
stringfiedResourceId = Integer.toString(getResourceId());
|
||||||
|
}
|
||||||
|
return stringfiedResourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterRepresentation() {
|
||||||
|
boolean resourceFound = getResourceId() != 0;
|
||||||
|
return (resourceFound ? getFilterValue() + " (" : "") + resourceName + (resourceFound ? ")" : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean filterUnavailable() {
|
||||||
|
boolean resourceNotFound = getResourceId() == 0;
|
||||||
|
if (resourceNotFound) {
|
||||||
|
Logger.printInfo(() -> "Resource id for " + resourceName + " was not found");
|
||||||
|
}
|
||||||
|
return resourceNotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class StringComponentFilter implements ComponentFilter {
|
||||||
|
|
||||||
|
public final String string;
|
||||||
|
|
||||||
|
public StringComponentFilter(String string) {
|
||||||
|
this.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String getFilterValue() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilterRepresentation() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package spotify.clienttoken.data.v0;
|
||||||
|
|
||||||
|
option optimize_for = LITE_RUNTIME;
|
||||||
|
option java_package = "app.revanced.extension.spotify.misc.fix.clienttoken.data.v0";
|
||||||
|
|
||||||
|
message ClientTokenRequest {
|
||||||
|
ClientTokenRequestType request_type = 1;
|
||||||
|
|
||||||
|
oneof request {
|
||||||
|
ClientDataRequest client_data = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientTokenRequestType {
|
||||||
|
REQUEST_UNKNOWN = 0;
|
||||||
|
REQUEST_CLIENT_DATA_REQUEST = 1;
|
||||||
|
REQUEST_CHALLENGE_ANSWERS_REQUEST = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientDataRequest {
|
||||||
|
string client_version = 1;
|
||||||
|
string client_id = 2;
|
||||||
|
|
||||||
|
oneof data {
|
||||||
|
ConnectivitySdkData connectivity_sdk_data = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectivitySdkData {
|
||||||
|
PlatformSpecificData platform_specific_data = 1;
|
||||||
|
string device_id = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PlatformSpecificData {
|
||||||
|
oneof data {
|
||||||
|
NativeIOSData ios = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message NativeIOSData {
|
||||||
|
int32 user_interface_idiom = 1;
|
||||||
|
bool target_iphone_simulator = 2;
|
||||||
|
string hw_machine = 3;
|
||||||
|
string system_version = 4;
|
||||||
|
string simulator_model_identifier = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ClientTokenResponse {
|
||||||
|
ClientTokenResponseType response_type = 1;
|
||||||
|
|
||||||
|
oneof response {
|
||||||
|
GrantedTokenResponse granted_token = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientTokenResponseType {
|
||||||
|
RESPONSE_UNKNOWN = 0;
|
||||||
|
RESPONSE_GRANTED_TOKEN_RESPONSE = 1;
|
||||||
|
RESPONSE_CHALLENGES_RESPONSE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GrantedTokenResponse {
|
||||||
|
string token = 1;
|
||||||
|
int32 expires_after_seconds = 2;
|
||||||
|
int32 refresh_after_seconds = 3;
|
||||||
|
repeated TokenDomain domains = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TokenDomain {
|
||||||
|
string domain = 1;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@@ -7,7 +7,7 @@ android {
|
|||||||
compileSdk = 34
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package app.revanced;
|
||||||
|
|
||||||
|
public interface ContextMenuItemPlaceholder {
|
||||||
|
Object getViewModel();
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.spotify.browsita.v1.resolved;
|
||||||
|
|
||||||
|
public final class Section {
|
||||||
|
public static final int BRAND_ADS_FIELD_NUMBER = 6;
|
||||||
|
public int sectionTypeCase_;
|
||||||
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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_;
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class SpoofSimPatch {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.initializationException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
|
Logger.printException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
android.namespace = "app.revanced.extension"
|
android.namespace = "app.revanced.extension"
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
package app.revanced.extension.youtube;
|
|
||||||
|
|
||||||
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;
|
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
|
|
||||||
public class ThemeHelper {
|
|
||||||
@Nullable
|
|
||||||
private static Integer darkThemeColor, lightThemeColor;
|
|
||||||
private static int themeValue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public static void setTheme(Enum<?> value) {
|
|
||||||
final int newOrdinalValue = value.ordinal();
|
|
||||||
if (themeValue != newOrdinalValue) {
|
|
||||||
themeValue = newOrdinalValue;
|
|
||||||
Logger.printDebug(() -> "Theme value: " + newOrdinalValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isDarkTheme() {
|
|
||||||
return themeValue == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setActivityTheme(Activity activity) {
|
|
||||||
final var theme = isDarkTheme()
|
|
||||||
? "Theme.YouTube.Settings.Dark"
|
|
||||||
: "Theme.YouTube.Settings";
|
|
||||||
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("SameReturnValue")
|
|
||||||
private static String darkThemeResourceName() {
|
|
||||||
// Value is changed by Theme patch, if included.
|
|
||||||
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 = getThemeColor(darkThemeResourceName(), Color.BLACK);
|
|
||||||
}
|
|
||||||
return darkThemeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("SameReturnValue")
|
|
||||||
private static String lightThemeResourceName() {
|
|
||||||
// Value is changed by Theme patch, if included.
|
|
||||||
return "@color/yt_white1";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The light theme color as specified by the Theme patch (if included),
|
|
||||||
* or the non dark mode background color unpatched YT uses.
|
|
||||||
*/
|
|
||||||
public static int getLightThemeColor() {
|
|
||||||
if (lightThemeColor == null) {
|
|
||||||
lightThemeColor = getThemeColor(lightThemeResourceName(), Color.WHITE);
|
|
||||||
}
|
|
||||||
return lightThemeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getBackgroundColor() {
|
|
||||||
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getForegroundColor() {
|
|
||||||
return isDarkTheme() ? getLightThemeColor() : getDarkThemeColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getDialogBackgroundColor() {
|
|
||||||
final String colorName = isDarkTheme()
|
|
||||||
? "yt_black1"
|
|
||||||
: "yt_white1";
|
|
||||||
|
|
||||||
return Utils.getColorFromString(colorName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getToolbarBackgroundColor() {
|
|
||||||
final String colorName = isDarkTheme()
|
|
||||||
? "yt_black3"
|
|
||||||
: "yt_white1";
|
|
||||||
|
|
||||||
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,101 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ChangeHeaderPatch {
|
||||||
|
|
||||||
|
public enum HeaderLogo {
|
||||||
|
DEFAULT(null, null),
|
||||||
|
REGULAR("ytWordmarkHeader", "yt_ringo2_wordmark_header"),
|
||||||
|
PREMIUM("ytPremiumWordmarkHeader", "yt_ringo2_premium_wordmark_header"),
|
||||||
|
REVANCED("revanced_header_logo", "revanced_header_logo"),
|
||||||
|
REVANCED_MINIMAL("revanced_header_logo_minimal", "revanced_header_logo_minimal"),
|
||||||
|
CUSTOM("custom_header", "custom_header");
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final String attributeName;
|
||||||
|
@Nullable
|
||||||
|
private final String drawableName;
|
||||||
|
|
||||||
|
HeaderLogo(@Nullable String attributeName, @Nullable String drawableName) {
|
||||||
|
this.attributeName = attributeName;
|
||||||
|
this.drawableName = drawableName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The attribute id of this header logo, or NULL if the logo should not be replaced.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private Integer getAttributeId() {
|
||||||
|
if (attributeName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int identifier = Utils.getResourceIdentifier(attributeName, "attr");
|
||||||
|
if (identifier == 0) {
|
||||||
|
// Identifier is zero if custom header setting was included in imported settings
|
||||||
|
// and a custom image was not included during patching.
|
||||||
|
Logger.printDebug(() -> "Could not find attribute: " + drawableName);
|
||||||
|
Settings.HEADER_LOGO.resetToDefault();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Drawable getDrawable() {
|
||||||
|
if (drawableName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String drawableFullName = drawableName + (Utils.isDarkModeEnabled()
|
||||||
|
? "_dark"
|
||||||
|
: "_light");
|
||||||
|
|
||||||
|
final int identifier = Utils.getResourceIdentifier(drawableFullName, "drawable");
|
||||||
|
if (identifier == 0) {
|
||||||
|
Logger.printDebug(() -> "Could not find drawable: " + drawableFullName);
|
||||||
|
Settings.HEADER_LOGO.resetToDefault();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Utils.getContext().getDrawable(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int getHeaderAttributeId(int original) {
|
||||||
|
return Objects.requireNonNullElse(Settings.HEADER_LOGO.get().getAttributeId(), original);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Drawable getDrawable(Drawable original) {
|
||||||
|
Drawable logo = Settings.HEADER_LOGO.get().getDrawable();
|
||||||
|
if (logo != null) {
|
||||||
|
return logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: If 'Hide Doodles' is enabled, this will force the regular logo regardless
|
||||||
|
// what account the user has. This can be improved the next time a Doodle is
|
||||||
|
// active and the attribute id is passed to this method so the correct
|
||||||
|
// regular/premium logo is returned.
|
||||||
|
logo = HeaderLogo.REGULAR.getDrawable();
|
||||||
|
if (logo != null) {
|
||||||
|
return logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should never happen.
|
||||||
|
Logger.printException(() -> "Could not find regular header logo resource");
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,10 @@ package app.revanced.extension.youtube.patches;
|
|||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
@@ -63,18 +66,28 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Utils.runOnMainThread(() -> {
|
Utils.runOnMainThread(() -> {
|
||||||
var alert = new android.app.AlertDialog.Builder(context)
|
try {
|
||||||
.setTitle(str("revanced_check_watch_history_domain_name_dialog_title"))
|
// Create the custom dialog.
|
||||||
.setMessage(Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")))
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
.setIconAttribute(android.R.attr.alertDialogIcon)
|
context,
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
str("revanced_check_watch_history_domain_name_dialog_title"), // Title.
|
||||||
dialog.dismiss();
|
Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")), // Message (HTML).
|
||||||
}).setNegativeButton(str("revanced_check_watch_history_domain_name_dialog_ignore"), (dialog, which) -> {
|
null, // No EditText.
|
||||||
Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false);
|
null, // OK button text.
|
||||||
dialog.dismiss();
|
() -> {}, // OK button action (just dismiss).
|
||||||
}).create();
|
() -> {}, // Cancel button action (just dismiss).
|
||||||
|
str("revanced_check_watch_history_domain_name_dialog_ignore"), // Neutral button text.
|
||||||
|
() -> Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false), // Neutral button action (Ignore).
|
||||||
|
true // Dismiss dialog on Neutral button click.
|
||||||
|
);
|
||||||
|
|
||||||
Utils.showDialog(context, alert, false, null);
|
// Show the dialog.
|
||||||
|
Dialog dialog = dialogPair.first;
|
||||||
|
|
||||||
|
Utils.showDialog(context, dialog, false, null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "checkDnsResolver dialog creation failure", ex);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "checkDnsResolver failure", ex);
|
Logger.printException(() -> "checkDnsResolver failure", ex);
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class DisableDoubleTapActionsPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* @return If "should skip to chapter start" flag is set.
|
||||||
|
*/
|
||||||
|
public static boolean disableDoubleTapChapters(boolean original) {
|
||||||
|
return original && !Settings.DISABLE_CHAPTER_SKIP_DOUBLE_TAP.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
@@ -58,6 +59,22 @@ public final class HidePlayerOverlayButtonsPatch {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void hidePlayerControlButtonsBackground(View rootView) {
|
||||||
|
try {
|
||||||
|
if (!Settings.HIDE_PLAYER_CONTROL_BUTTONS_BACKGROUND.get()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each button is an ImageView with a background set to another drawable.
|
||||||
|
removeImageViewsBackgroundRecursive(rootView);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "removePlayerControlButtonsBackground failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void hideView(View parentView, int resourceId) {
|
private static void hideView(View parentView, int resourceId) {
|
||||||
View nextPreviousButton = parentView.findViewById(resourceId);
|
View nextPreviousButton = parentView.findViewById(resourceId);
|
||||||
|
|
||||||
@@ -69,4 +86,16 @@ public final class HidePlayerOverlayButtonsPatch {
|
|||||||
Logger.printDebug(() -> "Hiding previous/next button");
|
Logger.printDebug(() -> "Hiding previous/next button");
|
||||||
Utils.hideViewByRemovingFromParentUnderCondition(true, nextPreviousButton);
|
Utils.hideViewByRemovingFromParentUnderCondition(true, nextPreviousButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void removeImageViewsBackgroundRecursive(View currentView) {
|
||||||
|
if (currentView instanceof ImageView imageView) {
|
||||||
|
imageView.setBackground(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentView instanceof ViewGroup viewGroup) {
|
||||||
|
for (int i = 0; i < viewGroup.getChildCount(); i++) {
|
||||||
|
removeImageViewsBackgroundRecursive(viewGroup.getChildAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,6 @@ public final class HideRelatedVideoOverlayPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean hideRelatedVideoOverlay() {
|
public static boolean hideRelatedVideoOverlay() {
|
||||||
return Settings.HIDE_RELATED_VIDEO_OVERLAY.get();
|
return Settings.HIDE_RELATED_VIDEOS_OVERLAY.get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,14 +153,12 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.containsAny(conversionContextString,
|
if (Utils.containsAny(conversionContextString,
|
||||||
"|shorts_dislike_button.eml", "|reel_dislike_button.eml"
|
"|shorts_dislike_button.eml", "|reel_dislike_button.eml")) {
|
||||||
)) {
|
|
||||||
return getShortsSpan(original, true);
|
return getShortsSpan(original, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.containsAny(conversionContextString,
|
if (Utils.containsAny(conversionContextString,
|
||||||
"|shorts_like_button.eml", "|reel_like_button.eml"
|
"|shorts_like_button.eml", "|reel_like_button.eml")) {
|
||||||
)) {
|
|
||||||
if (!Utils.containsNumber(original)) {
|
if (!Utils.containsNumber(original)) {
|
||||||
Logger.printDebug(() -> "Replacing hidden likes count");
|
Logger.printDebug(() -> "Replacing hidden likes count");
|
||||||
return getShortsSpan(original, false);
|
return getShortsSpan(original, false);
|
||||||
|
|||||||
@@ -7,11 +7,17 @@ public class VersionCheckPatch {
|
|||||||
return Utils.getAppVersionName().compareTo(version) >= 0;
|
return Utils.getAppVersionName().compareTo(version) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
|
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
|
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
|
public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
|
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
|
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
|
||||||
|
@Deprecated
|
||||||
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.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");
|
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,16 @@ package app.revanced.extension.youtube.patches.announcements;
|
|||||||
|
|
||||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS;
|
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS;
|
||||||
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS;
|
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.util.Pair;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
@@ -56,10 +59,11 @@ public final class AnnouncementsPatch {
|
|||||||
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
int id = Settings.ANNOUNCEMENT_LAST_ID.defaultValue;
|
||||||
try {
|
try {
|
||||||
final var announcementIds = new JSONArray(jsonString);
|
final var announcementIds = new JSONArray(jsonString);
|
||||||
id = announcementIds.getJSONObject(0).getInt("id");
|
if (announcementIds.length() == 0) return true;
|
||||||
|
|
||||||
|
id = announcementIds.getJSONObject(0).getInt("id");
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
Logger.printException(() -> "Failed to parse announcement IDs", ex);
|
Logger.printException(() -> "Failed to parse announcement ID", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not show the announcement, if the last announcement id is the same as the current one.
|
// Do not show the announcement, if the last announcement id is the same as the current one.
|
||||||
@@ -120,25 +124,38 @@ public final class AnnouncementsPatch {
|
|||||||
final Level finalLevel = level;
|
final Level finalLevel = level;
|
||||||
|
|
||||||
Utils.runOnMainThread(() -> {
|
Utils.runOnMainThread(() -> {
|
||||||
// Show the announcement.
|
// Create the custom dialog and show the announcement.
|
||||||
var alert = new AlertDialog.Builder(context)
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
.setTitle(finalTitle)
|
context,
|
||||||
.setMessage(finalMessage)
|
finalTitle, // Title.
|
||||||
.setIcon(finalLevel.icon)
|
finalMessage, // Message.
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
null, // No EditText.
|
||||||
Settings.ANNOUNCEMENT_LAST_ID.save(finalId);
|
null, // OK button text.
|
||||||
dialog.dismiss();
|
() -> Settings.ANNOUNCEMENT_LAST_ID.save(finalId), // OK button action.
|
||||||
}).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> {
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
dialog.dismiss();
|
str("revanced_announcements_dialog_dismiss"), // Neutral button text.
|
||||||
})
|
() -> {}, // Neutral button action (dismiss only).
|
||||||
.setCancelable(false)
|
true // Dismiss dialog when onNeutralClick.
|
||||||
.create();
|
);
|
||||||
|
|
||||||
Utils.showDialog(context, alert, false, (AlertDialog dialog) -> {
|
Dialog dialog = dialogPair.first;
|
||||||
// Make links clickable.
|
LinearLayout mainLayout = dialogPair.second;
|
||||||
((TextView) dialog.findViewById(android.R.id.message))
|
|
||||||
.setMovementMethod(LinkMovementMethod.getInstance());
|
// Set the icon for the title TextView
|
||||||
});
|
for (int i = 0, childCould = mainLayout.getChildCount(); i < childCould; i++) {
|
||||||
|
View child = mainLayout.getChildAt(i);
|
||||||
|
if (child instanceof TextView childTextView && finalTitle.equals(childTextView.getText().toString())) {
|
||||||
|
childTextView.setCompoundDrawablesWithIntrinsicBounds(
|
||||||
|
finalLevel.icon, 0, 0, 0);
|
||||||
|
childTextView.setCompoundDrawablePadding(dipToPixels(8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set dialog as non-cancelable.
|
||||||
|
dialog.setCancelable(false);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
Utils.showDialog(context, dialog);
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
final var message = "Failed to get announcement";
|
final var message = "Failed to get announcement";
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import static app.revanced.extension.shared.requests.Route.Method.GET;
|
|||||||
|
|
||||||
public class AnnouncementsRoutes {
|
public class AnnouncementsRoutes {
|
||||||
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v4";
|
private static final String ANNOUNCEMENTS_PROVIDER = "https://api.revanced.app/v4";
|
||||||
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=youtube");
|
public static final Route GET_LATEST_ANNOUNCEMENT_IDS = new Route(GET, "/announcements/latest/id?tag=\uD83C\uDF9E\uFE0F%20YouTube");
|
||||||
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=youtube");
|
public static final Route GET_LATEST_ANNOUNCEMENTS = new Route(GET, "/announcements/latest?tag=\uD83C\uDF9E\uFE0F%20YouTube");
|
||||||
|
|
||||||
private AnnouncementsRoutes() {
|
private AnnouncementsRoutes() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,10 +34,6 @@ public final class AdsFilter extends Filter {
|
|||||||
private final StringFilterGroup playerShoppingShelf;
|
private final StringFilterGroup playerShoppingShelf;
|
||||||
private final ByteArrayFilterGroup playerShoppingShelfBuffer;
|
private final ByteArrayFilterGroup playerShoppingShelfBuffer;
|
||||||
|
|
||||||
private final StringFilterGroup channelProfile;
|
|
||||||
private final ByteArrayFilterGroup visitStoreButton;
|
|
||||||
|
|
||||||
private final StringFilterGroup shoppingLinks;
|
|
||||||
|
|
||||||
public AdsFilter() {
|
public AdsFilter() {
|
||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
@@ -91,6 +87,7 @@ public final class AdsFilter extends Filter {
|
|||||||
"text_image_no_button_layout", // Tablet layout search results.
|
"text_image_no_button_layout", // Tablet layout search results.
|
||||||
"video_display_button_group_layout",
|
"video_display_button_group_layout",
|
||||||
"video_display_carousel_button_group_layout",
|
"video_display_carousel_button_group_layout",
|
||||||
|
"video_display_carousel_buttoned_short_dr_layout",
|
||||||
"video_display_full_buttoned_short_dr_layout",
|
"video_display_full_buttoned_short_dr_layout",
|
||||||
"video_display_full_layout",
|
"video_display_full_layout",
|
||||||
"watch_metadata_app_promo"
|
"watch_metadata_app_promo"
|
||||||
@@ -107,37 +104,25 @@ public final class AdsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final var viewProducts = new StringFilterGroup(
|
final var viewProducts = new StringFilterGroup(
|
||||||
Settings.HIDE_PRODUCTS_BANNER,
|
Settings.HIDE_VIEW_PRODUCTS_BANNER,
|
||||||
"product_item",
|
"product_item",
|
||||||
"products_in_video",
|
"products_in_video",
|
||||||
"shopping_overlay.eml", // Video player overlay shopping links.
|
"shopping_overlay.eml" // Video player overlay shopping links.
|
||||||
"shopping_carousel.eml" // Channel profile shopping shelf.
|
|
||||||
);
|
);
|
||||||
|
|
||||||
shoppingLinks = new StringFilterGroup(
|
final var shoppingLinks = new StringFilterGroup(
|
||||||
Settings.HIDE_SHOPPING_LINKS,
|
Settings.HIDE_SHOPPING_LINKS,
|
||||||
"expandable_list"
|
"shopping_description_shelf.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
playerShoppingShelf = new StringFilterGroup(
|
playerShoppingShelf = new StringFilterGroup(
|
||||||
Settings.HIDE_PLAYER_STORE_SHELF,
|
Settings.HIDE_CREATOR_STORE_SHELF,
|
||||||
"horizontal_shelf.eml"
|
"horizontal_shelf.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
playerShoppingShelfBuffer = new ByteArrayFilterGroup(
|
playerShoppingShelfBuffer = new ByteArrayFilterGroup(
|
||||||
null,
|
null,
|
||||||
"shopping_item_card_list.eml"
|
"shopping_item_card_list"
|
||||||
);
|
|
||||||
|
|
||||||
channelProfile = new StringFilterGroup(
|
|
||||||
Settings.HIDE_VISIT_STORE_BUTTON,
|
|
||||||
"channel_profile.eml",
|
|
||||||
"page_header.eml"
|
|
||||||
);
|
|
||||||
|
|
||||||
visitStoreButton = new ByteArrayFilterGroup(
|
|
||||||
null,
|
|
||||||
"header_store_button"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final var webLinkPanel = new StringFilterGroup(
|
final var webLinkPanel = new StringFilterGroup(
|
||||||
@@ -147,7 +132,8 @@ public final class AdsFilter extends Filter {
|
|||||||
|
|
||||||
final var merchandise = new StringFilterGroup(
|
final var merchandise = new StringFilterGroup(
|
||||||
Settings.HIDE_MERCHANDISE_BANNERS,
|
Settings.HIDE_MERCHANDISE_BANNERS,
|
||||||
"product_carousel"
|
"product_carousel",
|
||||||
|
"shopping_carousel.eml" // Channel profile shopping shelf.
|
||||||
);
|
);
|
||||||
|
|
||||||
final var selfSponsor = new StringFilterGroup(
|
final var selfSponsor = new StringFilterGroup(
|
||||||
@@ -156,16 +142,15 @@ public final class AdsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
|
fullscreenAd,
|
||||||
generalAds,
|
generalAds,
|
||||||
merchandise,
|
merchandise,
|
||||||
viewProducts,
|
movieAds,
|
||||||
selfSponsor,
|
|
||||||
fullscreenAd,
|
|
||||||
channelProfile,
|
|
||||||
webLinkPanel,
|
|
||||||
shoppingLinks,
|
|
||||||
playerShoppingShelf,
|
playerShoppingShelf,
|
||||||
movieAds
|
selfSponsor,
|
||||||
|
shoppingLinks,
|
||||||
|
viewProducts,
|
||||||
|
webLinkPanel
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,11 +161,6 @@ public final class AdsFilter extends Filter {
|
|||||||
return contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered();
|
return contentIndex == 0 && playerShoppingShelfBuffer.check(protobufBufferArray).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for the index because of likelihood of false positives.
|
|
||||||
if (contentIndex != 0 && matchedGroup == shoppingLinks) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exceptions.matches(path)) {
|
if (exceptions.matches(path)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -192,10 +172,6 @@ public final class AdsFilter extends Filter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == channelProfile) {
|
|
||||||
return visitStoreButton.check(protobufBufferArray).isFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQual
|
|||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abuse LithoFilter for {@link AdvancedVideoQualityMenuPatch}.
|
* LithoFilter for {@link AdvancedVideoQualityMenuPatch}.
|
||||||
*/
|
*/
|
||||||
public final class AdvancedVideoQualityMenuFilter extends Filter {
|
public final class AdvancedVideoQualityMenuFilter extends Filter {
|
||||||
// Must be volatile or synchronized, as litho filtering runs off main thread
|
// Must be volatile or synchronized, as litho filtering runs off main thread
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ final class ButtonsFilter extends Filter {
|
|||||||
"|download_button.eml"
|
"|download_button.eml"
|
||||||
),
|
),
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_PLAYLIST_BUTTON,
|
Settings.HIDE_SAVE_BUTTON,
|
||||||
"|save_to_playlist_button"
|
"|save_to_playlist_button"
|
||||||
),
|
),
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
@@ -76,6 +76,10 @@ final class ButtonsFilter extends Filter {
|
|||||||
Settings.HIDE_ASK_BUTTON,
|
Settings.HIDE_ASK_BUTTON,
|
||||||
"yt_fill_spark"
|
"yt_fill_spark"
|
||||||
),
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_STOP_ADS_BUTTON,
|
||||||
|
"yt_outline_slash_circle_left"
|
||||||
|
),
|
||||||
// Check for clip button both here and using a path filter,
|
// Check for clip button both here and using a path filter,
|
||||||
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
// as there's a chance the path is a generic action button and won't contain 'clip_button'
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
|
|||||||
@@ -3,16 +3,12 @@ package app.revanced.extension.youtube.patches.components;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
final class CommentsFilter extends Filter {
|
final class CommentsFilter extends Filter {
|
||||||
|
|
||||||
private static final String TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH
|
private final StringFilterGroup chipBar;
|
||||||
= "|CellType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|";
|
|
||||||
|
|
||||||
private final StringFilterGroup commentComposer;
|
|
||||||
private final ByteArrayFilterGroup emojiPickerBufferGroup;
|
|
||||||
private final StringFilterGroup filterChipBar;
|
|
||||||
private final ByteArrayFilterGroup aiCommentsSummary;
|
private final ByteArrayFilterGroup aiCommentsSummary;
|
||||||
|
|
||||||
public CommentsFilter() {
|
public CommentsFilter() {
|
||||||
@@ -21,6 +17,21 @@ final class CommentsFilter extends Filter {
|
|||||||
"live_chat_summary_banner.eml"
|
"live_chat_summary_banner.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
chipBar = new StringFilterGroup(
|
||||||
|
Settings.HIDE_COMMENTS_AI_SUMMARY,
|
||||||
|
"chip_bar.eml"
|
||||||
|
);
|
||||||
|
|
||||||
|
aiCommentsSummary = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"yt_fill_spark_"
|
||||||
|
);
|
||||||
|
|
||||||
|
var channelGuidelines = new StringFilterGroup(
|
||||||
|
Settings.HIDE_COMMENTS_CHANNEL_GUIDELINES,
|
||||||
|
"channel_guidelines_entry_banner"
|
||||||
|
);
|
||||||
|
|
||||||
var commentsByMembers = new StringFilterGroup(
|
var commentsByMembers = new StringFilterGroup(
|
||||||
Settings.HIDE_COMMENTS_BY_MEMBERS_HEADER,
|
Settings.HIDE_COMMENTS_BY_MEMBERS_HEADER,
|
||||||
"sponsorships_comments_header.eml",
|
"sponsorships_comments_header.eml",
|
||||||
@@ -33,6 +44,11 @@ final class CommentsFilter extends Filter {
|
|||||||
"_comments"
|
"_comments"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var communityGuidelines = new StringFilterGroup(
|
||||||
|
Settings.HIDE_COMMENTS_COMMUNITY_GUIDELINES,
|
||||||
|
"community_guidelines"
|
||||||
|
);
|
||||||
|
|
||||||
var createAShort = new StringFilterGroup(
|
var createAShort = new StringFilterGroup(
|
||||||
Settings.HIDE_COMMENTS_CREATE_A_SHORT_BUTTON,
|
Settings.HIDE_COMMENTS_CREATE_A_SHORT_BUTTON,
|
||||||
"composer_short_creation_button.eml"
|
"composer_short_creation_button.eml"
|
||||||
@@ -50,51 +66,33 @@ final class CommentsFilter extends Filter {
|
|||||||
"super_thanks_button.eml"
|
"super_thanks_button.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
commentComposer = new StringFilterGroup(
|
StringFilterGroup timestampButton = new StringFilterGroup(
|
||||||
Settings.HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS,
|
Settings.HIDE_COMMENTS_TIMESTAMP_BUTTON,
|
||||||
"comment_composer.eml"
|
"composer_timestamp_button.eml"
|
||||||
);
|
|
||||||
|
|
||||||
emojiPickerBufferGroup = new ByteArrayFilterGroup(
|
|
||||||
null,
|
|
||||||
"id.comment.quick_emoji.button"
|
|
||||||
);
|
|
||||||
|
|
||||||
filterChipBar = new StringFilterGroup(
|
|
||||||
Settings.HIDE_COMMENTS_AI_SUMMARY,
|
|
||||||
"filter_chip_bar.eml"
|
|
||||||
);
|
|
||||||
|
|
||||||
aiCommentsSummary = new ByteArrayFilterGroup(
|
|
||||||
null,
|
|
||||||
"yt_fill_spark_"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
|
channelGuidelines,
|
||||||
chatSummary,
|
chatSummary,
|
||||||
|
chipBar,
|
||||||
commentsByMembers,
|
commentsByMembers,
|
||||||
comments,
|
comments,
|
||||||
|
communityGuidelines,
|
||||||
createAShort,
|
createAShort,
|
||||||
previewComment,
|
previewComment,
|
||||||
thanksButton,
|
thanksButton,
|
||||||
commentComposer,
|
timestampButton
|
||||||
filterChipBar
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
if (matchedGroup == commentComposer) {
|
if (matchedGroup == chipBar) {
|
||||||
// To completely hide the emoji buttons (and leave no empty space), the timestamp button is
|
// Playlist sort button uses same components and must only filter if the player is opened.
|
||||||
// also hidden because the buffer is exactly the same and there's no way selectively hide.
|
return PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||||
return contentIndex == 0
|
&& aiCommentsSummary.check(protobufBufferArray).isFiltered();
|
||||||
&& path.endsWith(TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH)
|
|
||||||
&& emojiPickerBufferGroup.check(protobufBufferArray).isFiltered();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchedGroup == filterChipBar) {
|
|
||||||
return aiCommentsSummary.check(protobufBufferArray).isFiltered();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import app.revanced.extension.youtube.StringTrieSearch;
|
import app.revanced.extension.youtube.StringTrieSearch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
final class DescriptionComponentsFilter extends Filter {
|
final class DescriptionComponentsFilter extends Filter {
|
||||||
@@ -14,6 +15,11 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
|
|
||||||
private final StringFilterGroup macroMarkersCarousel;
|
private final StringFilterGroup macroMarkersCarousel;
|
||||||
|
|
||||||
|
private final StringFilterGroup horizontalShelf;
|
||||||
|
private final ByteArrayFilterGroup cellVideoAttribute;
|
||||||
|
|
||||||
|
private final StringFilterGroup aiGeneratedVideoSummarySection;
|
||||||
|
|
||||||
public DescriptionComponentsFilter() {
|
public DescriptionComponentsFilter() {
|
||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
"compact_channel",
|
"compact_channel",
|
||||||
@@ -23,7 +29,7 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
"metadata"
|
"metadata"
|
||||||
);
|
);
|
||||||
|
|
||||||
final StringFilterGroup aiGeneratedVideoSummarySection = new StringFilterGroup(
|
aiGeneratedVideoSummarySection = new StringFilterGroup(
|
||||||
Settings.HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION,
|
Settings.HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION,
|
||||||
"cell_expandable_metadata.eml"
|
"cell_expandable_metadata.eml"
|
||||||
);
|
);
|
||||||
@@ -35,8 +41,7 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
|
|
||||||
final StringFilterGroup attributesSection = new StringFilterGroup(
|
final StringFilterGroup attributesSection = new StringFilterGroup(
|
||||||
Settings.HIDE_ATTRIBUTES_SECTION,
|
Settings.HIDE_ATTRIBUTES_SECTION,
|
||||||
"gaming_section",
|
// "gaming_section", "music_section"
|
||||||
"music_section",
|
|
||||||
"video_attributes_section"
|
"video_attributes_section"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -76,27 +81,48 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
horizontalShelf = new StringFilterGroup(
|
||||||
|
Settings.HIDE_ATTRIBUTES_SECTION,
|
||||||
|
"horizontal_shelf.eml"
|
||||||
|
);
|
||||||
|
|
||||||
|
cellVideoAttribute = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"cell_video_attribute"
|
||||||
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
aiGeneratedVideoSummarySection,
|
aiGeneratedVideoSummarySection,
|
||||||
askSection,
|
askSection,
|
||||||
attributesSection,
|
attributesSection,
|
||||||
infoCardsSection,
|
infoCardsSection,
|
||||||
|
horizontalShelf,
|
||||||
howThisWasMadeSection,
|
howThisWasMadeSection,
|
||||||
|
macroMarkersCarousel,
|
||||||
podcastSection,
|
podcastSection,
|
||||||
transcriptSection,
|
transcriptSection
|
||||||
macroMarkersCarousel
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
|
||||||
|
if (matchedGroup == aiGeneratedVideoSummarySection) {
|
||||||
|
// Only hide if player is open, in case this component is used somewhere else.
|
||||||
|
return PlayerType.getCurrent().isMaximizedOrFullscreen();
|
||||||
|
}
|
||||||
|
|
||||||
if (exceptions.matches(path)) return false;
|
if (exceptions.matches(path)) return false;
|
||||||
|
|
||||||
if (matchedGroup == macroMarkersCarousel) {
|
if (matchedGroup == macroMarkersCarousel) {
|
||||||
return contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered();
|
return contentIndex == 0 && macroMarkersCarouselGroupList.check(protobufBufferArray).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matchedGroup == horizontalShelf) {
|
||||||
|
return cellVideoAttribute.check(protobufBufferArray).isFiltered();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButt
|
|||||||
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.StringTrieSearch;
|
import app.revanced.extension.youtube.StringTrieSearch;
|
||||||
|
import app.revanced.extension.youtube.patches.ChangeHeaderPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
@@ -30,7 +32,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
private final StringFilterGroup inFeedSurvey;
|
private final StringFilterGroup surveys;
|
||||||
private final StringFilterGroup notifyMe;
|
private final StringFilterGroup notifyMe;
|
||||||
private final StringFilterGroup singleItemInformationPanel;
|
private final StringFilterGroup singleItemInformationPanel;
|
||||||
private final StringFilterGroup expandableMetadata;
|
private final StringFilterGroup expandableMetadata;
|
||||||
@@ -39,6 +41,9 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
private final ByteArrayFilterGroup joinMembershipButton;
|
private final ByteArrayFilterGroup joinMembershipButton;
|
||||||
private final StringFilterGroup horizontalShelves;
|
private final StringFilterGroup horizontalShelves;
|
||||||
private final ByteArrayFilterGroup ticketShelf;
|
private final ByteArrayFilterGroup ticketShelf;
|
||||||
|
private final StringFilterGroup chipBar;
|
||||||
|
private final StringFilterGroup channelProfile;
|
||||||
|
private final ByteArrayFilterGroupList channelProfileBuffer;
|
||||||
|
|
||||||
public LayoutComponentsFilter() {
|
public LayoutComponentsFilter() {
|
||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
@@ -80,18 +85,13 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"poll_post_responsive_root.eml"
|
"poll_post_responsive_root.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var communityGuidelines = new StringFilterGroup(
|
|
||||||
Settings.HIDE_COMMUNITY_GUIDELINES,
|
|
||||||
"community_guidelines"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var subscribersCommunityGuidelines = new StringFilterGroup(
|
final var subscribersCommunityGuidelines = new StringFilterGroup(
|
||||||
Settings.HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES,
|
Settings.HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES,
|
||||||
"sponsorships_comments_upsell"
|
"sponsorships_comments_upsell"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var channelMemberShelf = new StringFilterGroup(
|
final var channelMembersShelf = new StringFilterGroup(
|
||||||
Settings.HIDE_CHANNEL_MEMBER_SHELF,
|
Settings.HIDE_MEMBERS_SHELF,
|
||||||
"member_recognition_shelf"
|
"member_recognition_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -105,8 +105,13 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"subscriptions_chip_bar"
|
"subscriptions_chip_bar"
|
||||||
);
|
);
|
||||||
|
|
||||||
inFeedSurvey = new StringFilterGroup(
|
chipBar = new StringFilterGroup(
|
||||||
Settings.HIDE_FEED_SURVEY,
|
Settings.HIDE_FILTER_BAR_FEED_IN_HISTORY,
|
||||||
|
"chip_bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
surveys = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SURVEYS,
|
||||||
"in_feed_survey",
|
"in_feed_survey",
|
||||||
"slimline_survey",
|
"slimline_survey",
|
||||||
"feed_nudge"
|
"feed_nudge"
|
||||||
@@ -133,13 +138,13 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final var latestPosts = new StringFilterGroup(
|
final var latestPosts = new StringFilterGroup(
|
||||||
Settings.HIDE_HIDE_LATEST_POSTS,
|
Settings.HIDE_LATEST_POSTS,
|
||||||
"post_shelf"
|
"post_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var channelGuidelines = new StringFilterGroup(
|
final var channelLinksPreview = new StringFilterGroup(
|
||||||
Settings.HIDE_HIDE_CHANNEL_GUIDELINES,
|
Settings.HIDE_LINKS_PREVIEW,
|
||||||
"channel_guidelines_entry_banner"
|
"attribution.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var emergencyBox = new StringFilterGroup(
|
final var emergencyBox = new StringFilterGroup(
|
||||||
@@ -164,7 +169,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expandableMetadata = new StringFilterGroup(
|
expandableMetadata = new StringFilterGroup(
|
||||||
Settings.HIDE_EXPANDABLE_CHIP,
|
Settings.HIDE_EXPANDABLE_CARD,
|
||||||
"inline_expander"
|
"inline_expander"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -194,7 +199,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"image_shelf"
|
"image_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
final var timedReactions = new StringFilterGroup(
|
final var timedReactions = new StringFilterGroup(
|
||||||
Settings.HIDE_TIMED_REACTIONS,
|
Settings.HIDE_TIMED_REACTIONS,
|
||||||
"emoji_control_panel",
|
"emoji_control_panel",
|
||||||
@@ -221,7 +225,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"sponsorships"
|
"sponsorships"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
final var channelWatermark = new StringFilterGroup(
|
final var channelWatermark = new StringFilterGroup(
|
||||||
Settings.HIDE_VIDEO_CHANNEL_WATERMARK,
|
Settings.HIDE_VIDEO_CHANNEL_WATERMARK,
|
||||||
"featured_channel_watermark_overlay"
|
"featured_channel_watermark_overlay"
|
||||||
@@ -232,11 +235,27 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"mixed_content_shelf"
|
"mixed_content_shelf"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var searchResultRecommendationLabels = new StringFilterGroup(
|
final var videoRecommendationLabels = new StringFilterGroup(
|
||||||
Settings.HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS,
|
Settings.HIDE_VIDEO_RECOMMENDATION_LABELS,
|
||||||
"endorsement_header_footer.eml"
|
"endorsement_header_footer.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
channelProfile = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"channel_profile.eml",
|
||||||
|
"page_header.eml"
|
||||||
|
);
|
||||||
|
channelProfileBuffer = new ByteArrayFilterGroupList();
|
||||||
|
channelProfileBuffer.addAll(new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_VISIT_STORE_BUTTON,
|
||||||
|
"header_store_button"
|
||||||
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_VISIT_COMMUNITY_BUTTON,
|
||||||
|
"community_button"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
horizontalShelves = new StringFilterGroup(
|
horizontalShelves = new StringFilterGroup(
|
||||||
Settings.HIDE_HORIZONTAL_SHELVES,
|
Settings.HIDE_HORIZONTAL_SHELVES,
|
||||||
"horizontal_video_shelf.eml",
|
"horizontal_video_shelf.eml",
|
||||||
@@ -247,39 +266,40 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
|
|
||||||
ticketShelf = new ByteArrayFilterGroup(
|
ticketShelf = new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_TICKET_SHELF,
|
Settings.HIDE_TICKET_SHELF,
|
||||||
"ticket"
|
"ticket.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
expandableMetadata,
|
artistCard,
|
||||||
inFeedSurvey,
|
audioTrackButton,
|
||||||
notifyMe,
|
channelLinksPreview,
|
||||||
compactChannelBar,
|
channelMembersShelf,
|
||||||
communityPosts,
|
channelProfile,
|
||||||
paidPromotion,
|
|
||||||
searchResultRecommendationLabels,
|
|
||||||
latestPosts,
|
|
||||||
channelWatermark,
|
channelWatermark,
|
||||||
communityGuidelines,
|
chipBar,
|
||||||
|
compactBanner,
|
||||||
|
compactChannelBar,
|
||||||
|
compactChannelBarInner,
|
||||||
|
communityPosts,
|
||||||
|
emergencyBox,
|
||||||
|
expandableMetadata,
|
||||||
|
forYouShelf,
|
||||||
|
horizontalShelves,
|
||||||
|
imageShelf,
|
||||||
|
infoPanel,
|
||||||
|
latestPosts,
|
||||||
|
medicalPanel,
|
||||||
|
notifyMe,
|
||||||
|
paidPromotion,
|
||||||
playables,
|
playables,
|
||||||
quickActions,
|
quickActions,
|
||||||
relatedVideos,
|
relatedVideos,
|
||||||
compactBanner,
|
|
||||||
compactChannelBarInner,
|
|
||||||
medicalPanel,
|
|
||||||
infoPanel,
|
|
||||||
singleItemInformationPanel,
|
singleItemInformationPanel,
|
||||||
emergencyBox,
|
|
||||||
subscribersCommunityGuidelines,
|
subscribersCommunityGuidelines,
|
||||||
subscriptionsChipBar,
|
subscriptionsChipBar,
|
||||||
channelGuidelines,
|
surveys,
|
||||||
audioTrackButton,
|
|
||||||
artistCard,
|
|
||||||
timedReactions,
|
timedReactions,
|
||||||
imageShelf,
|
videoRecommendationLabels
|
||||||
channelMemberShelf,
|
|
||||||
forYouShelf,
|
|
||||||
horizontalShelves
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,10 +317,14 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
|
|
||||||
// The groups are excluded from the filter due to the exceptions list below.
|
// The groups are excluded from the filter due to the exceptions list below.
|
||||||
// Filter them separately here.
|
// Filter them separately here.
|
||||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata) {
|
if (matchedGroup == notifyMe || matchedGroup == surveys || matchedGroup == expandableMetadata) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matchedGroup == channelProfile) {
|
||||||
|
return channelProfileBuffer.check(protobufBufferArray).isFiltered();
|
||||||
|
}
|
||||||
|
|
||||||
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
||||||
|
|
||||||
if (matchedGroup == compactChannelBarInner) {
|
if (matchedGroup == compactChannelBarInner) {
|
||||||
@@ -314,6 +338,10 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
return contentIndex == 0 && (hideShelves() || ticketShelf.check(protobufBufferArray).isFiltered());
|
return contentIndex == 0 && (hideShelves() || ticketShelf.check(protobufBufferArray).isFiltered());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (matchedGroup == chipBar) {
|
||||||
|
return contentIndex == 0 && NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +349,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
* Called from a different place then the other filters.
|
* Called from a different place then the other filters.
|
||||||
*/
|
*/
|
||||||
public static boolean filterMixPlaylists(final Object conversionContext, @Nullable final byte[] bytes) {
|
public static boolean filterMixPlaylists(Object conversionContext, @Nullable final byte[] bytes) {
|
||||||
try {
|
try {
|
||||||
if (!Settings.HIDE_MIX_PLAYLISTS.get()) {
|
if (!Settings.HIDE_MIX_PLAYLISTS.get()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -411,13 +439,11 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
public static void setDoodleDrawable(ImageView imageView, Drawable original) {
|
||||||
public static Drawable hideYoodles(Drawable animatedYoodle) {
|
Drawable replacement = HIDE_DOODLES_ENABLED
|
||||||
if (HIDE_DOODLES_ENABLED) {
|
? ChangeHeaderPatch.getDrawable(original)
|
||||||
return null;
|
: original;
|
||||||
}
|
imageView.setImageDrawable(replacement);
|
||||||
|
|
||||||
return animatedYoodle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final boolean HIDE_SHOW_MORE_BUTTON_ENABLED = Settings.HIDE_SHOW_MORE_BUTTON.get();
|
private static final boolean HIDE_SHOW_MORE_BUTTON_ENABLED = Settings.HIDE_SHOW_MORE_BUTTON.get();
|
||||||
@@ -448,7 +474,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do not hide if the navigation back button is visible,
|
// Do not hide if the navigation back button is visible,
|
||||||
// otherwise the content shelves in the explore/music/courses pages are hidde.
|
// otherwise the content shelves in the explore/music/courses pages are hidden.
|
||||||
if (NavigationBar.isBackButtonVisible()) {
|
if (NavigationBar.isBackButtonVisible()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public final class LithoFilterPatch {
|
|||||||
/**
|
/**
|
||||||
* Search through a byte array for all ASCII strings.
|
* Search through a byte array for all ASCII strings.
|
||||||
*/
|
*/
|
||||||
private static void findAsciiStrings(StringBuilder builder, byte[] buffer) {
|
static void findAsciiStrings(StringBuilder builder, byte[] buffer) {
|
||||||
// Valid ASCII values (ignore control characters).
|
// Valid ASCII values (ignore control characters).
|
||||||
final int minimumAscii = 32; // 32 = space character
|
final int minimumAscii = 32; // 32 = space character
|
||||||
final int maximumAscii = 126; // 127 = delete character
|
final int maximumAscii = 126; // 127 = delete character
|
||||||
@@ -74,8 +74,29 @@ public final class LithoFilterPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Litho layout fixed thread pool size override.
|
||||||
|
* <p>
|
||||||
|
* Unpatched YouTube uses a layout fixed thread pool between 1 and 3 threads:
|
||||||
|
* <pre>
|
||||||
|
* 1 thread - > Device has less than 6 cores
|
||||||
|
* 2 threads -> Device has over 6 cores and less than 6GB of memory
|
||||||
|
* 3 threads -> Device has over 6 cores and more than 6GB of memory
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* Using more than 1 thread causes layout issues such as the You tab watch/playlist shelf
|
||||||
|
* that is sometimes incorrectly hidden (ReVanced is not hiding it), and seems to
|
||||||
|
* fix a race issue if using the active navigation tab status with litho filtering.
|
||||||
|
*/
|
||||||
|
private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for actual filters.
|
||||||
|
*/
|
||||||
|
private static final class DummyFilter extends Filter { }
|
||||||
|
|
||||||
private static final Filter[] filters = new Filter[] {
|
private static final Filter[] filters = new Filter[] {
|
||||||
new DummyFilter() // Replaced by patch.
|
new DummyFilter() // Replaced patching, do not touch.
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
|
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
|
||||||
@@ -87,11 +108,7 @@ public final class LithoFilterPatch {
|
|||||||
* Because litho filtering is multi-threaded and the buffer is passed in from a different injection point,
|
* Because litho filtering is multi-threaded and the buffer is passed in from a different injection point,
|
||||||
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
|
||||||
*/
|
*/
|
||||||
private static final ThreadLocal<ByteBuffer> bufferThreadLocal = new ThreadLocal<>();
|
private static final ThreadLocal<byte[]> bufferThreadLocal = new ThreadLocal<>();
|
||||||
/**
|
|
||||||
* Results of calling {@link #filter(String, StringBuilder)}.
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<Boolean> filterResult = new ThreadLocal<>();
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (Filter filter : filters) {
|
for (Filter filter : filters) {
|
||||||
@@ -147,57 +164,50 @@ public final class LithoFilterPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point. Called off the main thread.
|
* Injection point. Called off the main thread.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
public static void setProtoBuffer(byte[] buffer) {
|
||||||
public static void setProtoBuffer(@Nullable ByteBuffer protobufBuffer) {
|
|
||||||
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
|
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
|
||||||
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
|
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
|
||||||
// The buffer will be cleared from memory after a new buffer is set by the same thread,
|
// The buffer will be cleared from memory after a new buffer is set by the same thread,
|
||||||
// or when the calling thread eventually dies.
|
// or when the calling thread eventually dies.
|
||||||
if (protobufBuffer == null) {
|
bufferThreadLocal.set(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Called off the main thread.
|
||||||
|
* Targets 20.21 and lower.
|
||||||
|
*/
|
||||||
|
public static void setProtoBuffer(@Nullable ByteBuffer buffer) {
|
||||||
|
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
|
||||||
|
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
|
||||||
|
// The buffer will be cleared from memory after a new buffer is set by the same thread,
|
||||||
|
// or when the calling thread eventually dies.
|
||||||
|
if (buffer == null || !buffer.hasArray()) {
|
||||||
// It appears the buffer can be cleared out just before the call to #filter()
|
// It appears the buffer can be cleared out just before the call to #filter()
|
||||||
// Ignore this null value and retain the last buffer that was set.
|
// Ignore this null value and retain the last buffer that was set.
|
||||||
Logger.printDebug(() -> "Ignoring null protobuffer");
|
Logger.printDebug(() -> "Ignoring null or empty buffer: " + buffer);
|
||||||
} else {
|
} else {
|
||||||
bufferThreadLocal.set(protobufBuffer);
|
setProtoBuffer(buffer.array());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean shouldFilter() {
|
public static boolean shouldFilter(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
|
||||||
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.
|
|
||||||
*/
|
|
||||||
public static void filter(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
|
|
||||||
filterResult.set(handleFiltering(lithoIdentifier, pathBuilder));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean handleFiltering(@Nullable String lithoIdentifier, StringBuilder pathBuilder) {
|
|
||||||
try {
|
try {
|
||||||
if (pathBuilder.length() == 0) {
|
if (pathBuilder.length() == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer protobufBuffer = bufferThreadLocal.get();
|
byte[] buffer = bufferThreadLocal.get();
|
||||||
final byte[] bufferArray;
|
|
||||||
// Potentially the buffer may have been null or never set up until now.
|
// Potentially the buffer may have been null or never set up until now.
|
||||||
// Use an empty buffer so the litho id/path filters still work correctly.
|
// Use an empty buffer so the litho id/path filters still work correctly.
|
||||||
if (protobufBuffer == null) {
|
if (buffer == null) {
|
||||||
bufferArray = EMPTY_BYTE_ARRAY;
|
buffer = EMPTY_BYTE_ARRAY;
|
||||||
} else if (!protobufBuffer.hasArray()) {
|
|
||||||
Logger.printDebug(() -> "Proto buffer does not have an array, using an empty buffer array");
|
|
||||||
bufferArray = EMPTY_BYTE_ARRAY;
|
|
||||||
} else {
|
|
||||||
bufferArray = protobufBuffer.array();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LithoFilterParameters parameter = new LithoFilterParameters(lithoIdentifier,
|
LithoFilterParameters parameter = new LithoFilterParameters(
|
||||||
pathBuilder.toString(), bufferArray);
|
lithoIdentifier, pathBuilder.toString(), buffer);
|
||||||
Logger.printDebug(() -> "Searching " + parameter);
|
Logger.printDebug(() -> "Searching " + parameter);
|
||||||
|
|
||||||
if (parameter.identifier != null && identifierSearchTree.matches(parameter.identifier, parameter)) {
|
if (parameter.identifier != null && identifierSearchTree.matches(parameter.identifier, parameter)) {
|
||||||
@@ -213,9 +223,28 @@ public final class LithoFilterPatch {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Placeholder for actual filters.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
final class DummyFilter extends Filter { }
|
public static int getExecutorCorePoolSize(int originalCorePoolSize) {
|
||||||
|
if (originalCorePoolSize != LITHO_LAYOUT_THREAD_POOL_SIZE) {
|
||||||
|
Logger.printDebug(() -> "Overriding core thread pool size from: " + originalCorePoolSize
|
||||||
|
+ " to: " + LITHO_LAYOUT_THREAD_POOL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LITHO_LAYOUT_THREAD_POOL_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int getExecutorMaxThreads(int originalMaxThreads) {
|
||||||
|
if (originalMaxThreads != LITHO_LAYOUT_THREAD_POOL_SIZE) {
|
||||||
|
Logger.printDebug(() -> "Overriding max thread pool size from: " + originalMaxThreads
|
||||||
|
+ " to: " + LITHO_LAYOUT_THREAD_POOL_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LITHO_LAYOUT_THREAD_POOL_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,8 +40,12 @@ public final class ShortsFilter extends Filter {
|
|||||||
|
|
||||||
private static WeakReference<PivotBar> pivotBarRef = new WeakReference<>(null);
|
private static WeakReference<PivotBar> pivotBarRef = new WeakReference<>(null);
|
||||||
|
|
||||||
private final StringFilterGroup shortsCompactFeedVideoPath;
|
private final StringFilterGroup shortsCompactFeedVideo;
|
||||||
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
||||||
|
private final StringFilterGroup useSoundButton;
|
||||||
|
private final ByteArrayFilterGroup useSoundButtonBuffer;
|
||||||
|
private final StringFilterGroup useTemplateButton;
|
||||||
|
private final ByteArrayFilterGroup useTemplateButtonBuffer;
|
||||||
|
|
||||||
private final StringFilterGroup subscribeButton;
|
private final StringFilterGroup subscribeButton;
|
||||||
private final StringFilterGroup joinButton;
|
private final StringFilterGroup joinButton;
|
||||||
@@ -49,11 +53,11 @@ public final class ShortsFilter extends Filter {
|
|||||||
private final StringFilterGroup shelfHeader;
|
private final StringFilterGroup shelfHeader;
|
||||||
|
|
||||||
private final StringFilterGroup suggestedAction;
|
private final StringFilterGroup suggestedAction;
|
||||||
private final ByteArrayFilterGroupList suggestedActionsGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList suggestedActionsBuffer = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
private final StringFilterGroup shortsActionBar;
|
private final StringFilterGroup shortsActionBar;
|
||||||
private final StringFilterGroup actionButton;
|
private final StringFilterGroup videoActionButton;
|
||||||
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList videoActionButtonBuffer = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
public ShortsFilter() {
|
public ShortsFilter() {
|
||||||
//
|
//
|
||||||
@@ -82,7 +86,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
// Path components.
|
// Path components.
|
||||||
//
|
//
|
||||||
|
|
||||||
shortsCompactFeedVideoPath = new StringFilterGroup(null,
|
shortsCompactFeedVideo = new StringFilterGroup(null,
|
||||||
// Shorts that appear in the feed/search when the device is using tablet layout.
|
// Shorts that appear in the feed/search when the device is using tablet layout.
|
||||||
"compact_video.eml",
|
"compact_video.eml",
|
||||||
// 'video_lockup_with_attachment.eml' is shown instead of 'compact_video.eml' for some users
|
// 'video_lockup_with_attachment.eml' is shown instead of 'compact_video.eml' for some users
|
||||||
@@ -174,7 +178,32 @@ public final class ShortsFilter extends Filter {
|
|||||||
"reel_action_bar.eml"
|
"reel_action_bar.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
actionButton = new StringFilterGroup(
|
useSoundButton = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_USE_SOUND_BUTTON,
|
||||||
|
// First filter needed for "Use this sound" that can appear when viewing Shorts
|
||||||
|
// through the "Short remixing this video" section.
|
||||||
|
"floating_action_button.eml",
|
||||||
|
// Second filter needed for "Use this sound" that can appear below the video title.
|
||||||
|
REEL_METAPANEL_PATH
|
||||||
|
);
|
||||||
|
|
||||||
|
useSoundButtonBuffer = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"yt_outline_camera_"
|
||||||
|
);
|
||||||
|
|
||||||
|
useTemplateButton = new StringFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON,
|
||||||
|
// Second filter needed for "Use this template" that can appear below the video title.
|
||||||
|
REEL_METAPANEL_PATH
|
||||||
|
);
|
||||||
|
|
||||||
|
useTemplateButtonBuffer = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
|
"yt_outline_template_add_"
|
||||||
|
);
|
||||||
|
|
||||||
|
videoActionButton = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
// Can be simply 'button.eml', 'shorts_video_action_button.eml' or 'reel_action_button.eml'
|
// Can be simply 'button.eml', 'shorts_video_action_button.eml' or 'reel_action_button.eml'
|
||||||
"button.eml"
|
"button.eml"
|
||||||
@@ -186,16 +215,16 @@ public final class ShortsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
shortsCompactFeedVideoPath, joinButton, subscribeButton, paidPromotionButton,
|
shortsCompactFeedVideo, joinButton, subscribeButton, paidPromotionButton,
|
||||||
shortsActionBar, suggestedAction, pausedOverlayButtons, channelBar,
|
shortsActionBar, suggestedAction, pausedOverlayButtons, channelBar,
|
||||||
fullVideoLinkLabel, videoTitle, reelSoundMetadata, soundButton, infoPanel,
|
fullVideoLinkLabel, videoTitle, useSoundButton, reelSoundMetadata, soundButton, infoPanel,
|
||||||
stickers, likeFountain, likeButton, dislikeButton
|
stickers, likeFountain, likeButton, dislikeButton
|
||||||
);
|
);
|
||||||
|
|
||||||
//
|
//
|
||||||
// All other action buttons.
|
// All other action buttons.
|
||||||
//
|
//
|
||||||
videoActionButtonGroupList.addAll(
|
videoActionButtonBuffer.addAll(
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
|
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
|
||||||
"reel_comment_button",
|
"reel_comment_button",
|
||||||
@@ -216,7 +245,13 @@ public final class ShortsFilter extends Filter {
|
|||||||
//
|
//
|
||||||
// Suggested actions.
|
// Suggested actions.
|
||||||
//
|
//
|
||||||
suggestedActionsGroupList.addAll(
|
suggestedActionsBuffer.addAll(
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_PREVIEW_COMMENT,
|
||||||
|
// Preview comment that can popup while a Short is playing.
|
||||||
|
// Uses no bundled icons, and instead the users profile photo is shown.
|
||||||
|
"shorts-comments-panel"
|
||||||
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_SHOP_BUTTON,
|
Settings.HIDE_SHORTS_SHOP_BUTTON,
|
||||||
"yt_outline_bag_"
|
"yt_outline_bag_"
|
||||||
@@ -236,10 +271,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
"yt_outline_bookmark_",
|
"yt_outline_bookmark_",
|
||||||
// 'Save sound' button. It seems this has been removed and only 'Save music' is used.
|
// 'Save sound' button. It seems this has been removed and only 'Save music' is used.
|
||||||
// Still hide this in case it's still present.
|
// Still hide this in case it's still present.
|
||||||
"yt_outline_list_add_",
|
"yt_outline_list_add_"
|
||||||
// 'Use this sound' button. It seems this has been removed and only 'Save music' is used.
|
|
||||||
// Still hide this in case it's still present.
|
|
||||||
"yt_outline_camera_"
|
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_SEARCH_SUGGESTIONS,
|
Settings.HIDE_SHORTS_SEARCH_SUGGESTIONS,
|
||||||
@@ -251,16 +283,26 @@ public final class ShortsFilter extends Filter {
|
|||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON,
|
Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON,
|
||||||
|
// "Use this template" can appear in two different places.
|
||||||
"yt_outline_template_add_"
|
"yt_outline_template_add_"
|
||||||
),
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_UPCOMING_BUTTON,
|
Settings.HIDE_SHORTS_UPCOMING_BUTTON,
|
||||||
"yt_outline_bell_"
|
"yt_outline_bell_"
|
||||||
),
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_EFFECT_BUTTON,
|
||||||
|
// https://www.gstatic.com/youtube/effects/xeno/arcade/effects/icons/
|
||||||
|
"/arcade/effects/icons/"
|
||||||
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_GREEN_SCREEN_BUTTON,
|
Settings.HIDE_SHORTS_GREEN_SCREEN_BUTTON,
|
||||||
"greenscreen_temp"
|
"greenscreen_temp"
|
||||||
),
|
),
|
||||||
|
new ByteArrayFilterGroup(
|
||||||
|
Settings.HIDE_SHORTS_NEW_POSTS_BUTTON,
|
||||||
|
"yt_outline_box_pencil"
|
||||||
|
),
|
||||||
new ByteArrayFilterGroup(
|
new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_SHORTS_HASHTAG_BUTTON,
|
Settings.HIDE_SHORTS_HASHTAG_BUTTON,
|
||||||
"yt_outline_hashtag_"
|
"yt_outline_hashtag_"
|
||||||
@@ -269,7 +311,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isEverySuggestedActionFilterEnabled() {
|
private boolean isEverySuggestedActionFilterEnabled() {
|
||||||
for (ByteArrayFilterGroup group : suggestedActionsGroupList) {
|
for (ByteArrayFilterGroup group : suggestedActionsBuffer) {
|
||||||
if (!group.isEnabled()) {
|
if (!group.isEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -287,15 +329,23 @@ public final class ShortsFilter extends Filter {
|
|||||||
return path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH);
|
return path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == shortsCompactFeedVideoPath) {
|
if (matchedGroup == useSoundButton) {
|
||||||
|
return useSoundButtonBuffer.check(protobufBufferArray).isFiltered();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedGroup == useTemplateButton) {
|
||||||
|
return useTemplateButtonBuffer.check(protobufBufferArray).isFiltered();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchedGroup == shortsCompactFeedVideo) {
|
||||||
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered();
|
return shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Video action buttons (comment, share, remix) have the same path.
|
// Video action buttons (comment, share, remix) have the same path.
|
||||||
// Like and dislike are separate path filters and don't require buffer searching.
|
// Like and dislike are separate path filters and don't require buffer searching.
|
||||||
if (matchedGroup == shortsActionBar) {
|
if (matchedGroup == shortsActionBar) {
|
||||||
return actionButton.check(path).isFiltered()
|
return videoActionButton.check(path).isFiltered()
|
||||||
&& videoActionButtonGroupList.check(protobufBufferArray).isFiltered();
|
&& videoActionButtonBuffer.check(protobufBufferArray).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == suggestedAction) {
|
if (matchedGroup == suggestedAction) {
|
||||||
@@ -306,7 +356,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return suggestedActionsGroupList.check(protobufBufferArray).isFiltered();
|
return suggestedActionsBuffer.check(protobufBufferArray).isFiltered();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -372,17 +422,6 @@ public final class ShortsFilter extends Filter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point. Only used if patching older than 19.03.
|
|
||||||
* This hook may be obsolete even for old versions
|
|
||||||
* as they now use a litho layout like newer versions.
|
|
||||||
*/
|
|
||||||
public static void hideShortsShelf(final View shortsShelfView) {
|
|
||||||
if (shouldHideShortsFeedItems()) {
|
|
||||||
Utils.hideViewByLayoutParams(shortsShelfView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getSoundButtonSize(int original) {
|
public static int getSoundButtonSize(int original) {
|
||||||
if (Settings.HIDE_SHORTS_SOUND_BUTTON.get()) {
|
if (Settings.HIDE_SHORTS_SOUND_BUTTON.get()) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This patch contains the logic to always open the advanced video quality menu.
|
* This patch contains the logic to always open the advanced video quality menu.
|
||||||
* Two methods are required, because the quality menu is a RecyclerView in the new YouTube version
|
|
||||||
* and a ListView in the old one.
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class AdvancedVideoQualityMenuPatch {
|
public final class AdvancedVideoQualityMenuPatch {
|
||||||
@@ -76,7 +74,7 @@ public final class AdvancedVideoQualityMenuPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
*
|
||||||
* Used if spoofing to an old app version, and also used for the Shorts video quality flyout.
|
* Shorts video quality flyout.
|
||||||
*/
|
*/
|
||||||
public static void showAdvancedVideoQualityMenu(ListView listView) {
|
public static void showAdvancedVideoQualityMenu(ListView listView) {
|
||||||
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
|
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
|
||||||
@@ -90,14 +88,12 @@ public final class AdvancedVideoQualityMenuPatch {
|
|||||||
final var indexOfAdvancedQualityMenuItem = 4;
|
final var indexOfAdvancedQualityMenuItem = 4;
|
||||||
if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return;
|
if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return;
|
||||||
|
|
||||||
Logger.printDebug(() -> "Found advanced menu item in old type of quality menu");
|
|
||||||
|
|
||||||
listView.setSoundEffectsEnabled(false);
|
listView.setSoundEffectsEnabled(false);
|
||||||
final var qualityItemMenuPosition = 4;
|
final var qualityItemMenuPosition = 4;
|
||||||
listView.performItemClick(null, qualityItemMenuPosition, 0);
|
listView.performItemClick(null, qualityItemMenuPosition, 0);
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "showOldVideoQualityMenu failure", ex);
|
Logger.printException(() -> "showAdvancedVideoQualityMenu failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,10 +64,11 @@ public class RememberVideoQualityPatch {
|
|||||||
else videoQualityWifi.save(defaultQuality);
|
else videoQualityWifi.save(defaultQuality);
|
||||||
networkTypeMessage = str("revanced_remember_video_quality_wifi");
|
networkTypeMessage = str("revanced_remember_video_quality_wifi");
|
||||||
}
|
}
|
||||||
Utils.showToastShort(str(
|
if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get())
|
||||||
useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast",
|
Utils.showToastShort(str(
|
||||||
networkTypeMessage, (defaultQuality + "p")
|
useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast",
|
||||||
));
|
networkTypeMessage, (defaultQuality + "p")
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -19,13 +19,15 @@ import android.graphics.drawable.ShapeDrawable;
|
|||||||
import android.graphics.drawable.shapes.RoundRectShape;
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
import android.icu.text.NumberFormat;
|
import android.icu.text.NumberFormat;
|
||||||
import android.support.v7.widget.RecyclerView;
|
import android.support.v7.widget.RecyclerView;
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewParent;
|
import android.view.ViewParent;
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.TranslateAnimation;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.GridLayout;
|
import android.widget.GridLayout;
|
||||||
@@ -39,7 +41,6 @@ import java.util.function.Function;
|
|||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
|
||||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||||
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch;
|
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
@@ -59,6 +60,11 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
*/
|
*/
|
||||||
public static final float PLAYBACK_SPEED_MAXIMUM = 8;
|
public static final float PLAYBACK_SPEED_MAXIMUM = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How much +/- speed adjustment buttons change the current speed.
|
||||||
|
*/
|
||||||
|
private static final double SPEED_ADJUSTMENT_CHANGE = 0.05;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scale used to convert user speed to {@link android.widget.ProgressBar#setProgress(int)}.
|
* Scale used to convert user speed to {@link android.widget.ProgressBar#setProgress(int)}.
|
||||||
*/
|
*/
|
||||||
@@ -240,6 +246,9 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
// Store the dialog reference.
|
// Store the dialog reference.
|
||||||
currentDialog = new WeakReference<>(dialog);
|
currentDialog = new WeakReference<>(dialog);
|
||||||
|
|
||||||
|
// Enable dismissing the dialog when tapping outside.
|
||||||
|
dialog.setCanceledOnTouchOutside(true);
|
||||||
|
|
||||||
// Create main vertical LinearLayout for dialog content.
|
// Create main vertical LinearLayout for dialog content.
|
||||||
LinearLayout mainLayout = new LinearLayout(context);
|
LinearLayout mainLayout = new LinearLayout(context);
|
||||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||||
@@ -259,15 +268,15 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
|
|
||||||
// Set rounded rectangle background for the main layout.
|
// Set rounded rectangle background for the main layout.
|
||||||
RoundRectShape roundRectShape = new RoundRectShape(
|
RoundRectShape roundRectShape = new RoundRectShape(
|
||||||
createCornerRadii(12), null, null);
|
Utils.createCornerRadii(12), null, null);
|
||||||
ShapeDrawable background = new ShapeDrawable(roundRectShape);
|
ShapeDrawable background = new ShapeDrawable(roundRectShape);
|
||||||
background.getPaint().setColor(ThemeHelper.getDialogBackgroundColor());
|
background.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||||
mainLayout.setBackground(background);
|
mainLayout.setBackground(background);
|
||||||
|
|
||||||
// Add handle bar at the top.
|
// Add handle bar at the top.
|
||||||
View handleBar = new View(context);
|
View handleBar = new View(context);
|
||||||
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
|
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
|
||||||
createCornerRadii(4), null, null));
|
Utils.createCornerRadii(4), null, null));
|
||||||
handleBackground.getPaint().setColor(getAdjustedBackgroundColor(true));
|
handleBackground.getPaint().setColor(getAdjustedBackgroundColor(true));
|
||||||
handleBar.setBackground(handleBackground);
|
handleBar.setBackground(handleBackground);
|
||||||
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(
|
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(
|
||||||
@@ -285,7 +294,7 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
float currentSpeed = VideoInformation.getPlaybackSpeed();
|
float currentSpeed = VideoInformation.getPlaybackSpeed();
|
||||||
// Initially show with only 0 minimum digits, so 1.0 shows as 1x
|
// Initially show with only 0 minimum digits, so 1.0 shows as 1x
|
||||||
currentSpeedText.setText(formatSpeedStringX(currentSpeed, 0));
|
currentSpeedText.setText(formatSpeedStringX(currentSpeed, 0));
|
||||||
currentSpeedText.setTextColor(ThemeHelper.getForegroundColor());
|
currentSpeedText.setTextColor(Utils.getAppForegroundColor());
|
||||||
currentSpeedText.setTextSize(16);
|
currentSpeedText.setTextSize(16);
|
||||||
currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD);
|
currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD);
|
||||||
currentSpeedText.setGravity(Gravity.CENTER);
|
currentSpeedText.setGravity(Gravity.CENTER);
|
||||||
@@ -305,7 +314,8 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
// Create minus button.
|
// Create minus button.
|
||||||
Button minusButton = new Button(context, null, 0); // Disable default theme style.
|
Button minusButton = new Button(context, null, 0); // Disable default theme style.
|
||||||
minusButton.setText(""); // No text on button.
|
minusButton.setText(""); // No text on button.
|
||||||
ShapeDrawable minusBackground = new ShapeDrawable(new RoundRectShape(createCornerRadii(20), null, null));
|
ShapeDrawable minusBackground = new ShapeDrawable(new RoundRectShape(
|
||||||
|
Utils.createCornerRadii(20), null, null));
|
||||||
minusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
minusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||||
minusButton.setBackground(minusBackground);
|
minusButton.setBackground(minusBackground);
|
||||||
OutlineSymbolDrawable minusDrawable = new OutlineSymbolDrawable(false); // Minus symbol.
|
OutlineSymbolDrawable minusDrawable = new OutlineSymbolDrawable(false); // Minus symbol.
|
||||||
@@ -319,9 +329,9 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax));
|
speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax));
|
||||||
speedSlider.setProgress(speedToProgressValue(currentSpeed));
|
speedSlider.setProgress(speedToProgressValue(currentSpeed));
|
||||||
speedSlider.getProgressDrawable().setColorFilter(
|
speedSlider.getProgressDrawable().setColorFilter(
|
||||||
ThemeHelper.getForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar.
|
Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar.
|
||||||
speedSlider.getThumb().setColorFilter(
|
speedSlider.getThumb().setColorFilter(
|
||||||
ThemeHelper.getForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb.
|
Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb.
|
||||||
LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams(
|
LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams(
|
||||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
|
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
|
||||||
sliderParams.setMargins(dip5, 0, dip5, 0); // 5dp to -/+ buttons.
|
sliderParams.setMargins(dip5, 0, dip5, 0); // 5dp to -/+ buttons.
|
||||||
@@ -331,7 +341,7 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
Button plusButton = new Button(context, null, 0); // Disable default theme style.
|
Button plusButton = new Button(context, null, 0); // Disable default theme style.
|
||||||
plusButton.setText(""); // No text on button.
|
plusButton.setText(""); // No text on button.
|
||||||
ShapeDrawable plusBackground = new ShapeDrawable(new RoundRectShape(
|
ShapeDrawable plusBackground = new ShapeDrawable(new RoundRectShape(
|
||||||
createCornerRadii(20), null, null));
|
Utils.createCornerRadii(20), null, null));
|
||||||
plusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
plusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||||
plusButton.setBackground(plusBackground);
|
plusButton.setBackground(plusBackground);
|
||||||
OutlineSymbolDrawable plusDrawable = new OutlineSymbolDrawable(true); // Plus symbol.
|
OutlineSymbolDrawable plusDrawable = new OutlineSymbolDrawable(true); // Plus symbol.
|
||||||
@@ -385,9 +395,9 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
});
|
});
|
||||||
|
|
||||||
minusButton.setOnClickListener(v -> userSelectedSpeed.apply(
|
minusButton.setOnClickListener(v -> userSelectedSpeed.apply(
|
||||||
VideoInformation.getPlaybackSpeed() - 0.05f));
|
(float) (VideoInformation.getPlaybackSpeed() - SPEED_ADJUSTMENT_CHANGE)));
|
||||||
plusButton.setOnClickListener(v -> userSelectedSpeed.apply(
|
plusButton.setOnClickListener(v -> userSelectedSpeed.apply(
|
||||||
VideoInformation.getPlaybackSpeed() + 0.05f));
|
(float) (VideoInformation.getPlaybackSpeed() + SPEED_ADJUSTMENT_CHANGE)));
|
||||||
|
|
||||||
// Create GridLayout for preset speed buttons.
|
// Create GridLayout for preset speed buttons.
|
||||||
GridLayout gridLayout = new GridLayout(context);
|
GridLayout gridLayout = new GridLayout(context);
|
||||||
@@ -418,13 +428,13 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
// Create speed button.
|
// Create speed button.
|
||||||
Button speedButton = new Button(context, null, 0);
|
Button speedButton = new Button(context, null, 0);
|
||||||
speedButton.setText(speedFormatter.format(speed)); // Do not use 'x' speed format.
|
speedButton.setText(speedFormatter.format(speed)); // Do not use 'x' speed format.
|
||||||
speedButton.setTextColor(ThemeHelper.getForegroundColor());
|
speedButton.setTextColor(Utils.getAppForegroundColor());
|
||||||
speedButton.setTextSize(12);
|
speedButton.setTextSize(12);
|
||||||
speedButton.setAllCaps(false);
|
speedButton.setAllCaps(false);
|
||||||
speedButton.setGravity(Gravity.CENTER);
|
speedButton.setGravity(Gravity.CENTER);
|
||||||
|
|
||||||
ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape(
|
ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape(
|
||||||
createCornerRadii(20), null, null));
|
Utils.createCornerRadii(20), null, null));
|
||||||
buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
|
||||||
speedButton.setBackground(buttonBackground);
|
speedButton.setBackground(buttonBackground);
|
||||||
speedButton.setPadding(dip5, dip5, dip5, dip5);
|
speedButton.setPadding(dip5, dip5, dip5, dip5);
|
||||||
@@ -442,7 +452,7 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
TextView normalLabel = new TextView(context);
|
TextView normalLabel = new TextView(context);
|
||||||
// Use same 'Normal' string as stock YouTube.
|
// Use same 'Normal' string as stock YouTube.
|
||||||
normalLabel.setText(str("normal_playback_rate_label"));
|
normalLabel.setText(str("normal_playback_rate_label"));
|
||||||
normalLabel.setTextColor(ThemeHelper.getForegroundColor());
|
normalLabel.setTextColor(Utils.getAppForegroundColor());
|
||||||
normalLabel.setTextSize(10);
|
normalLabel.setTextSize(10);
|
||||||
normalLabel.setGravity(Gravity.CENTER);
|
normalLabel.setGravity(Gravity.CENTER);
|
||||||
|
|
||||||
@@ -489,6 +499,77 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
window.setBackgroundDrawable(null); // Remove default dialog background.
|
window.setBackgroundDrawable(null); // Remove default dialog background.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply slide-in animation when showing the dialog.
|
||||||
|
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
|
||||||
|
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
|
||||||
|
slideInABottomAnimation.setDuration(fadeDurationFast);
|
||||||
|
mainLayout.startAnimation(slideInABottomAnimation);
|
||||||
|
|
||||||
|
// Set touch listener on mainLayout to enable drag-to-dismiss.
|
||||||
|
//noinspection ClickableViewAccessibility
|
||||||
|
mainLayout.setOnTouchListener(new View.OnTouchListener() {
|
||||||
|
/** Threshold for dismissing the dialog. */
|
||||||
|
final float dismissThreshold = dipToPixels(100); // Distance to drag to dismiss.
|
||||||
|
/** Store initial Y position of touch. */
|
||||||
|
float touchY;
|
||||||
|
/** Track current translation. */
|
||||||
|
float translationY;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
switch (event.getAction()) {
|
||||||
|
case MotionEvent.ACTION_DOWN:
|
||||||
|
// Capture initial Y position of touch.
|
||||||
|
touchY = event.getRawY();
|
||||||
|
translationY = mainLayout.getTranslationY();
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_MOVE:
|
||||||
|
// Calculate drag distance and apply translation downwards only.
|
||||||
|
final float deltaY = event.getRawY() - touchY;
|
||||||
|
// Only allow downward drag (positive deltaY).
|
||||||
|
if (deltaY >= 0) {
|
||||||
|
mainLayout.setTranslationY(translationY + deltaY);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case MotionEvent.ACTION_UP:
|
||||||
|
case MotionEvent.ACTION_CANCEL:
|
||||||
|
// Check if dialog should be dismissed based on drag distance.
|
||||||
|
if (mainLayout.getTranslationY() > dismissThreshold) {
|
||||||
|
// Animate dialog off-screen and dismiss.
|
||||||
|
//noinspection ExtractMethodRecommender
|
||||||
|
final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels
|
||||||
|
- mainLayout.getTop();
|
||||||
|
TranslateAnimation slideOut = new TranslateAnimation(
|
||||||
|
0, 0, mainLayout.getTranslationY(), remainingDistance);
|
||||||
|
slideOut.setDuration(fadeDurationFast);
|
||||||
|
slideOut.setAnimationListener(new Animation.AnimationListener() {
|
||||||
|
@Override
|
||||||
|
public void onAnimationStart(Animation animation) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationEnd(Animation animation) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAnimationRepeat(Animation animation) {}
|
||||||
|
});
|
||||||
|
mainLayout.startAnimation(slideOut);
|
||||||
|
} else {
|
||||||
|
// Animate back to original position if not dragged far enough.
|
||||||
|
TranslateAnimation slideBack = new TranslateAnimation(
|
||||||
|
0, 0, mainLayout.getTranslationY(), 0);
|
||||||
|
slideBack.setDuration(fadeDurationFast);
|
||||||
|
mainLayout.startAnimation(slideBack);
|
||||||
|
mainLayout.setTranslationY(0);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Create observer for PlayerType changes.
|
// Create observer for PlayerType changes.
|
||||||
Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() {
|
Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -515,27 +596,9 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss");
|
Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply slide-in animation when showing the dialog.
|
|
||||||
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
|
|
||||||
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
|
|
||||||
slideInABottomAnimation.setDuration(fadeDurationFast);
|
|
||||||
mainLayout.startAnimation(slideInABottomAnimation);
|
|
||||||
|
|
||||||
dialog.show(); // Display the dialog.
|
dialog.show(); // Display the dialog.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an array of corner radii for a rounded rectangle shape.
|
|
||||||
*
|
|
||||||
* @param dp The radius in density-independent pixels (dp) to apply to all corners.
|
|
||||||
* @return An array of eight float values representing the corner radii
|
|
||||||
* (top-left, top-right, bottom-right, bottom-left).
|
|
||||||
*/
|
|
||||||
private static float[] createCornerRadii(float dp) {
|
|
||||||
final float radius = dipToPixels(dp);
|
|
||||||
return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param speed The playback speed value to format.
|
* @param speed The playback speed value to format.
|
||||||
* @return A string representation of the speed with 'x' (e.g. "1.25x" or "1.00x").
|
* @return A string representation of the speed with 'x' (e.g. "1.25x" or "1.00x").
|
||||||
@@ -553,15 +616,21 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rounds the given playback speed to the nearest 0.05 increment and ensures it is within valid bounds.
|
* Rounds the given playback speed to the nearest 0.05 increment,
|
||||||
|
* unless the speed exactly matches a preset custom speed.
|
||||||
*
|
*
|
||||||
* @param speed The playback speed to round.
|
* @param speed The playback speed to round.
|
||||||
* @return The rounded speed, constrained to the specified bounds.
|
* @return The rounded speed, constrained to the specified bounds.
|
||||||
*/
|
*/
|
||||||
private static float roundSpeedToNearestIncrement(float speed) {
|
private static float roundSpeedToNearestIncrement(float speed) {
|
||||||
// Round to nearest 0.05 speed.
|
// Allow speed as-is if it exactly matches a speed preset such as 1.03x.
|
||||||
final float roundedSpeed = Math.round(speed / 0.05f) * 0.05f;
|
if (arrayContains(customPlaybackSpeeds, speed)) {
|
||||||
return Utils.clamp(roundedSpeed, 0.05f, PLAYBACK_SPEED_MAXIMUM);
|
return speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round to nearest 0.05 speed. Must use double precision otherwise rounding error can occur.
|
||||||
|
final double roundedSpeed = Math.round(speed / SPEED_ADJUSTMENT_CHANGE) * SPEED_ADJUSTMENT_CHANGE;
|
||||||
|
return Utils.clamp((float) roundedSpeed, (float) SPEED_ADJUSTMENT_CHANGE, PLAYBACK_SPEED_MAXIMUM);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -573,12 +642,12 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
* for light themes to ensure visual contrast.
|
* for light themes to ensure visual contrast.
|
||||||
*/
|
*/
|
||||||
public static int getAdjustedBackgroundColor(boolean isHandleBar) {
|
public static int getAdjustedBackgroundColor(boolean isHandleBar) {
|
||||||
final int baseColor = ThemeHelper.getDialogBackgroundColor();
|
final int baseColor = Utils.getDialogBackgroundColor();
|
||||||
float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme.
|
float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme.
|
||||||
float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme.
|
float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme.
|
||||||
return ThemeHelper.isDarkTheme()
|
return Utils.isDarkModeEnabled()
|
||||||
? ThemeHelper.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme.
|
? Utils.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme.
|
||||||
: ThemeHelper.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme.
|
: Utils.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +661,7 @@ class OutlineSymbolDrawable extends Drawable {
|
|||||||
OutlineSymbolDrawable(boolean isPlus) {
|
OutlineSymbolDrawable(boolean isPlus) {
|
||||||
this.isPlus = isPlus;
|
this.isPlus = isPlus;
|
||||||
paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Enable anti-aliasing for smooth rendering.
|
paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Enable anti-aliasing for smooth rendering.
|
||||||
paint.setColor(ThemeHelper.getForegroundColor());
|
paint.setColor(Utils.getAppForegroundColor());
|
||||||
paint.setStyle(Paint.Style.STROKE); // Use stroke style for outline.
|
paint.setStyle(Paint.Style.STROKE); // Use stroke style for outline.
|
||||||
paint.setStrokeWidth(dipToPixels(1)); // 1dp stroke width.
|
paint.setStrokeWidth(dipToPixels(1)); // 1dp stroke width.
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ public final class RememberPlaybackSpeedPatch {
|
|||||||
}
|
}
|
||||||
Settings.PLAYBACK_SPEED_DEFAULT.save(finalPlaybackSpeed);
|
Settings.PLAYBACK_SPEED_DEFAULT.save(finalPlaybackSpeed);
|
||||||
|
|
||||||
Utils.showToastShort(str("revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x")));
|
if (Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST.get())
|
||||||
|
Utils.showToastShort(str("revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x")));
|
||||||
}, TOAST_DELAY_MILLISECONDS);
|
}, TOAST_DELAY_MILLISECONDS);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package app.revanced.extension.youtube.patches.theme;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import static app.revanced.extension.shared.Utils.clamp;
|
import static app.revanced.extension.shared.Utils.clamp;
|
||||||
|
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
|
||||||
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
@@ -173,23 +174,15 @@ public final class SeekbarColorPatch {
|
|||||||
*/
|
*/
|
||||||
public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) {
|
public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) {
|
||||||
try {
|
try {
|
||||||
if (!SEEKBAR_CUSTOM_COLOR_ENABLED) {
|
SplashScreenAnimationStyle animationStyle = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
|
||||||
|
if (!SEEKBAR_CUSTOM_COLOR_ENABLED
|
||||||
|
// Black and white animations cannot use color replacements.
|
||||||
|
|| animationStyle == SplashScreenAnimationStyle.FPS_30_BLACK_AND_WHITE
|
||||||
|
|| animationStyle == SplashScreenAnimationStyle.FPS_60_BLACK_AND_WHITE) {
|
||||||
view.patch_setAnimation(resourceId);
|
view.patch_setAnimation(resourceId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
|
||||||
if (false) { // Set true to force slow animation for development.
|
|
||||||
final int longAnimation = Utils.getResourceIdentifier(
|
|
||||||
Utils.isDarkModeEnabled()
|
|
||||||
? "startup_animation_5s_30fps_dark"
|
|
||||||
: "startup_animation_5s_30fps_light",
|
|
||||||
"raw");
|
|
||||||
if (longAnimation != 0) {
|
|
||||||
resourceId = longAnimation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must specify primary key name otherwise the morphing YT logo color is also changed.
|
// Must specify primary key name otherwise the morphing YT logo color is also changed.
|
||||||
String originalKey = "\"k\":";
|
String originalKey = "\"k\":";
|
||||||
String originalPrimary = originalKey + "[1,0,0.2,1]";
|
String originalPrimary = originalKey + "[1,0,0.2,1]";
|
||||||
@@ -199,21 +192,16 @@ public final class SeekbarColorPatch {
|
|||||||
String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]);
|
String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]);
|
||||||
|
|
||||||
String json = loadRawResourceAsString(resourceId);
|
String json = loadRawResourceAsString(resourceId);
|
||||||
if (json == null) {
|
String replacement = json
|
||||||
return; // Should never happen.
|
.replace(originalPrimary, replacementPrimary)
|
||||||
}
|
.replace(originalAccent, replacementAccent);
|
||||||
|
|
||||||
if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) {
|
if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) {
|
||||||
String jsonFinal = json;
|
Logger.printException(() -> "Could not replace splash animation colors: " + json);
|
||||||
Logger.printException(() -> "Could not replace launch animation colors: " + jsonFinal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "Replacing Lottie animation JSON");
|
|
||||||
json = json.replace(originalPrimary, replacementPrimary);
|
|
||||||
json = json.replace(originalAccent, replacementAccent);
|
|
||||||
|
|
||||||
// cacheKey is not needed since the animation will not be reused.
|
// cacheKey is not needed since the animation will not be reused.
|
||||||
view.patch_setAnimation(new ByteArrayInputStream(json.getBytes()), null);
|
view.patch_setAnimation(new ByteArrayInputStream(replacement.getBytes()), null);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "setSplashAnimationLottie failure", ex);
|
Logger.printException(() -> "setSplashAnimationLottie failure", ex);
|
||||||
}
|
}
|
||||||
@@ -234,8 +222,7 @@ public final class SeekbarColorPatch {
|
|||||||
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
|
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
|
||||||
return scanner.next();
|
return scanner.next();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Logger.printException(() -> "Could not load resource: " + resourceId);
|
throw new IllegalStateException("Could not load resource: " + resourceId);
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,48 @@
|
|||||||
package app.revanced.extension.youtube.patches.theme;
|
package app.revanced.extension.youtube.patches.theme;
|
||||||
|
|
||||||
|
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle.styleFromOrdinal;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class ThemePatch {
|
public class ThemePatch {
|
||||||
|
|
||||||
|
public enum SplashScreenAnimationStyle {
|
||||||
|
DEFAULT(0),
|
||||||
|
FPS_60_ONE_SECOND(1),
|
||||||
|
FPS_60_TWO_SECOND(2),
|
||||||
|
FPS_60_FIVE_SECOND(3),
|
||||||
|
FPS_60_BLACK_AND_WHITE(4),
|
||||||
|
FPS_30_ONE_SECOND(5),
|
||||||
|
FPS_30_TWO_SECOND(6),
|
||||||
|
FPS_30_FIVE_SECOND(7),
|
||||||
|
FPS_30_BLACK_AND_WHITE(8);
|
||||||
|
// There exists a 10th json style used as the switch statement default,
|
||||||
|
// but visually it is identical to 60fps one second.
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
static SplashScreenAnimationStyle styleFromOrdinal(int style) {
|
||||||
|
// Alternatively can return using values()[style]
|
||||||
|
for (SplashScreenAnimationStyle value : values()) {
|
||||||
|
if (value.style == style) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int style;
|
||||||
|
|
||||||
|
SplashScreenAnimationStyle(int style) {
|
||||||
|
this.style = style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// color constants used in relation with litho components
|
// color constants used in relation with litho components
|
||||||
private static final int[] WHITE_VALUES = {
|
private static final int[] WHITE_VALUES = {
|
||||||
-1, // comments chip background
|
-1, // comments chip background
|
||||||
@@ -37,7 +74,7 @@ public class ThemePatch {
|
|||||||
* @return The new or original color value
|
* @return The new or original color value
|
||||||
*/
|
*/
|
||||||
public static int getValue(int originalValue) {
|
public static int getValue(int originalValue) {
|
||||||
if (ThemeHelper.isDarkTheme()) {
|
if (Utils.isDarkModeEnabled()) {
|
||||||
if (anyEquals(originalValue, DARK_VALUES)) return BLACK_COLOR;
|
if (anyEquals(originalValue, DARK_VALUES)) return BLACK_COLOR;
|
||||||
} else {
|
} else {
|
||||||
if (anyEquals(originalValue, WHITE_VALUES)) return WHITE_COLOR;
|
if (anyEquals(originalValue, WHITE_VALUES)) return WHITE_COLOR;
|
||||||
@@ -58,4 +95,22 @@ public class ThemePatch {
|
|||||||
public static boolean gradientLoadingScreenEnabled(boolean original) {
|
public static boolean gradientLoadingScreenEnabled(boolean original) {
|
||||||
return GRADIENT_LOADING_SCREEN_ENABLED;
|
return GRADIENT_LOADING_SCREEN_ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int getLoadingScreenType(int original) {
|
||||||
|
SplashScreenAnimationStyle style = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
|
||||||
|
if (style == SplashScreenAnimationStyle.DEFAULT) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int replacement = style.style;
|
||||||
|
if (original != replacement) {
|
||||||
|
Logger.printDebug(() -> "Overriding splash screen style from: "
|
||||||
|
+ styleFromOrdinal(original) + " to: " + style);
|
||||||
|
}
|
||||||
|
|
||||||
|
return replacement;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,11 +30,15 @@ import java.util.HashMap;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
|
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
|
||||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
@@ -177,7 +181,7 @@ public class ReturnYouTubeDislike {
|
|||||||
* Ideally, this would be the actual color YT uses at runtime.
|
* Ideally, this would be the actual color YT uses at runtime.
|
||||||
*/
|
*/
|
||||||
private static int getSeparatorColor() {
|
private static int getSeparatorColor() {
|
||||||
return ThemeHelper.isDarkTheme()
|
return Utils.isDarkModeEnabled()
|
||||||
? 0x33FFFFFF
|
? 0x33FFFFFF
|
||||||
: 0xFFD9D9D9;
|
: 0xFFD9D9D9;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
@@ -14,7 +16,6 @@ import app.revanced.extension.shared.Logger;
|
|||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.AppLanguage;
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
|
||||||
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
||||||
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
|
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||||
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
||||||
@@ -25,10 +26,15 @@ import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFrag
|
|||||||
* This class is responsible for injecting our own fragment by replacing the LicenseActivity.
|
* This class is responsible for injecting our own fragment by replacing the LicenseActivity.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class LicenseActivityHook {
|
public class LicenseActivityHook extends Activity {
|
||||||
|
|
||||||
|
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
|
||||||
|
|
||||||
private static ViewGroup.LayoutParams toolbarLayoutParams;
|
private static ViewGroup.LayoutParams toolbarLayoutParams;
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
public static SearchViewController searchViewController;
|
||||||
|
|
||||||
public static void setToolbarLayoutParams(Toolbar toolbar) {
|
public static void setToolbarLayoutParams(Toolbar toolbar) {
|
||||||
if (toolbarLayoutParams != null) {
|
if (toolbarLayoutParams != null) {
|
||||||
toolbar.setLayoutParams(toolbarLayoutParams);
|
toolbar.setLayoutParams(toolbarLayoutParams);
|
||||||
@@ -78,8 +84,8 @@ public class LicenseActivityHook {
|
|||||||
*/
|
*/
|
||||||
public static void initialize(Activity licenseActivity) {
|
public static void initialize(Activity licenseActivity) {
|
||||||
try {
|
try {
|
||||||
ThemeHelper.setActivityTheme(licenseActivity);
|
setActivityTheme(licenseActivity);
|
||||||
ThemeHelper.setNavigationBarColor(licenseActivity.getWindow());
|
ReVancedPreferenceFragment.setNavigationBarColor(licenseActivity.getWindow());
|
||||||
licenseActivity.setContentView(getResourceIdentifier(
|
licenseActivity.setContentView(getResourceIdentifier(
|
||||||
"revanced_settings_with_toolbar", "layout"));
|
"revanced_settings_with_toolbar", "layout"));
|
||||||
|
|
||||||
@@ -114,7 +120,7 @@ public class LicenseActivityHook {
|
|||||||
toolBarParent.removeView(dummyToolbar);
|
toolBarParent.removeView(dummyToolbar);
|
||||||
|
|
||||||
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
|
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
|
||||||
toolbar.setBackgroundColor(ThemeHelper.getToolbarBackgroundColor());
|
toolbar.setBackgroundColor(getToolbarBackgroundColor());
|
||||||
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
|
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
|
||||||
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
|
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
|
||||||
|
|
||||||
@@ -124,15 +130,52 @@ public class LicenseActivityHook {
|
|||||||
TextView toolbarTextView = Utils.getChildView(toolbar, false,
|
TextView toolbarTextView = Utils.getChildView(toolbar, false,
|
||||||
view -> view instanceof TextView);
|
view -> view instanceof TextView);
|
||||||
if (toolbarTextView != null) {
|
if (toolbarTextView != null) {
|
||||||
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor());
|
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
|
||||||
|
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||||
}
|
}
|
||||||
setToolbarLayoutParams(toolbar);
|
setToolbarLayoutParams(toolbar);
|
||||||
|
|
||||||
// Add Search Icon and EditText for ReVancedPreferenceFragment only.
|
// Add Search bar only for ReVancedPreferenceFragment.
|
||||||
if (fragment instanceof ReVancedPreferenceFragment) {
|
if (fragment instanceof ReVancedPreferenceFragment) {
|
||||||
SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
|
searchViewController = SearchViewController.addSearchViewComponents(activity, toolbar, (ReVancedPreferenceFragment) fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
toolBarParent.addView(toolbar, 0);
|
toolBarParent.addView(toolbar, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setActivityTheme(Activity activity) {
|
||||||
|
final var theme = Utils.isDarkModeEnabled()
|
||||||
|
? "Theme.YouTube.Settings.Dark"
|
||||||
|
: "Theme.YouTube.Settings";
|
||||||
|
activity.setTheme(getResourceIdentifier(theme, "style"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getToolbarBackgroundColor() {
|
||||||
|
final String colorName = Utils.isDarkModeEnabled()
|
||||||
|
? "yt_black3"
|
||||||
|
: "yt_white1";
|
||||||
|
|
||||||
|
return Utils.getColorFromString(colorName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Updates dark/light mode since YT settings can force light/dark mode
|
||||||
|
* which can differ from the global device settings.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static void updateLightDarkModeStatus(Enum<?> value) {
|
||||||
|
final int themeOrdinal = value.ordinal();
|
||||||
|
if (currentThemeValueOrdinal != themeOrdinal) {
|
||||||
|
currentThemeValueOrdinal = themeOrdinal;
|
||||||
|
Utils.setIsDarkModeEnabled(themeOrdinal == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void handleConfigurationChanged(Activity activity, Configuration newConfig) {
|
||||||
|
if (searchViewController != null) {
|
||||||
|
searchViewController.handleOrientationChange(newConfig.orientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import static app.revanced.extension.shared.StringRef.str;
|
|||||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.drawable.GradientDrawable;
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.view.inputmethod.InputMethodManager;
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.AutoCompleteTextView;
|
import android.widget.AutoCompleteTextView;
|
||||||
@@ -18,6 +20,7 @@ import android.widget.SearchView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -31,7 +34,6 @@ import app.revanced.extension.shared.Utils;
|
|||||||
import app.revanced.extension.shared.settings.AppLanguage;
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.StringSetting;
|
import app.revanced.extension.shared.settings.StringSetting;
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
|
||||||
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +52,7 @@ public class SearchViewController {
|
|||||||
private final Deque<String> searchHistory;
|
private final Deque<String> searchHistory;
|
||||||
private final AutoCompleteTextView autoCompleteTextView;
|
private final AutoCompleteTextView autoCompleteTextView;
|
||||||
private final boolean showSettingsSearchHistory;
|
private final boolean showSettingsSearchHistory;
|
||||||
|
private int currentOrientation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a background drawable for the SearchView with rounded corners.
|
* Creates a background drawable for the SearchView with rounded corners.
|
||||||
@@ -58,11 +61,7 @@ public class SearchViewController {
|
|||||||
GradientDrawable background = new GradientDrawable();
|
GradientDrawable background = new GradientDrawable();
|
||||||
background.setShape(GradientDrawable.RECTANGLE);
|
background.setShape(GradientDrawable.RECTANGLE);
|
||||||
background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius.
|
background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius.
|
||||||
int baseColor = ThemeHelper.getBackgroundColor();
|
background.setColor(getSearchViewBackground());
|
||||||
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;
|
return background;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,15 +71,22 @@ public class SearchViewController {
|
|||||||
private static GradientDrawable createSuggestionBackgroundDrawable(Context context) {
|
private static GradientDrawable createSuggestionBackgroundDrawable(Context context) {
|
||||||
GradientDrawable background = new GradientDrawable();
|
GradientDrawable background = new GradientDrawable();
|
||||||
background.setShape(GradientDrawable.RECTANGLE);
|
background.setShape(GradientDrawable.RECTANGLE);
|
||||||
background.setCornerRadius(8 * context.getResources().getDisplayMetrics().density); // 8dp corner radius.
|
background.setColor(getSearchViewBackground());
|
||||||
return background;
|
return background;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ColorInt
|
||||||
|
public static int getSearchViewBackground() {
|
||||||
|
return Utils.isDarkModeEnabled()
|
||||||
|
? Utils.adjustColorBrightness(Utils.getDialogBackgroundColor(), 1.11f)
|
||||||
|
: Utils.adjustColorBrightness(Utils.getThemeLightColor(), 0.95f);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds search view components to the activity.
|
* Adds search view components to the activity.
|
||||||
*/
|
*/
|
||||||
public static void addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
public static SearchViewController addSearchViewComponents(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
||||||
new SearchViewController(activity, toolbar, fragment);
|
return new SearchViewController(activity, toolbar, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
private SearchViewController(Activity activity, Toolbar toolbar, ReVancedPreferenceFragment fragment) {
|
||||||
@@ -89,6 +95,7 @@ public class SearchViewController {
|
|||||||
this.originalTitle = toolbar.getTitle();
|
this.originalTitle = toolbar.getTitle();
|
||||||
this.showSettingsSearchHistory = Settings.SETTINGS_SEARCH_HISTORY.get();
|
this.showSettingsSearchHistory = Settings.SETTINGS_SEARCH_HISTORY.get();
|
||||||
this.searchHistory = new LinkedList<>();
|
this.searchHistory = new LinkedList<>();
|
||||||
|
this.currentOrientation = activity.getResources().getConfiguration().orientation;
|
||||||
StringSetting searchEntries = Settings.SETTINGS_SEARCH_ENTRIES;
|
StringSetting searchEntries = Settings.SETTINGS_SEARCH_ENTRIES;
|
||||||
if (showSettingsSearchHistory) {
|
if (showSettingsSearchHistory) {
|
||||||
String entries = searchEntries.get();
|
String entries = searchEntries.get();
|
||||||
@@ -111,6 +118,9 @@ public class SearchViewController {
|
|||||||
searchView.getContext().getResources().getIdentifier(
|
searchView.getContext().getResources().getIdentifier(
|
||||||
"android:id/search_src_text", null, null));
|
"android:id/search_src_text", null, null));
|
||||||
|
|
||||||
|
// Disable fullscreen keyboard mode.
|
||||||
|
autoCompleteTextView.setImeOptions(autoCompleteTextView.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
|
||||||
|
|
||||||
// Set background and query hint.
|
// Set background and query hint.
|
||||||
searchView.setBackground(createBackgroundDrawable(toolbar.getContext()));
|
searchView.setBackground(createBackgroundDrawable(toolbar.getContext()));
|
||||||
searchView.setQueryHint(str("revanced_settings_search_hint"));
|
searchView.setQueryHint(str("revanced_settings_search_hint"));
|
||||||
@@ -171,10 +181,6 @@ public class SearchViewController {
|
|||||||
final int actionSearchId = getResourceIdentifier("action_search", "id");
|
final int actionSearchId = getResourceIdentifier("action_search", "id");
|
||||||
toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu"));
|
toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu"));
|
||||||
MenuItem searchItem = toolbar.getMenu().findItem(actionSearchId);
|
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.
|
// Set menu item click listener.
|
||||||
toolbar.setOnMenuItemClickListener(item -> {
|
toolbar.setOnMenuItemClickListener(item -> {
|
||||||
@@ -197,7 +203,7 @@ public class SearchViewController {
|
|||||||
if (isSearchActive) {
|
if (isSearchActive) {
|
||||||
closeSearch();
|
closeSearch();
|
||||||
} else {
|
} else {
|
||||||
activity.onBackPressed();
|
activity.finish();
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "navigation click failure", ex);
|
Logger.printException(() -> "navigation click failure", ex);
|
||||||
@@ -285,6 +291,16 @@ public class SearchViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void handleOrientationChange(int newOrientation) {
|
||||||
|
if (newOrientation != currentOrientation) {
|
||||||
|
currentOrientation = newOrientation;
|
||||||
|
if (autoCompleteTextView != null) {
|
||||||
|
autoCompleteTextView.dismissDropDown();
|
||||||
|
Logger.printDebug(() -> "Orientation changed, search history dismissed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the search view and shows the keyboard.
|
* Opens the search view and shows the keyboard.
|
||||||
*/
|
*/
|
||||||
@@ -313,15 +329,10 @@ public class SearchViewController {
|
|||||||
/**
|
/**
|
||||||
* Closes the search view and hides the keyboard.
|
* Closes the search view and hides the keyboard.
|
||||||
*/
|
*/
|
||||||
private void closeSearch() {
|
public void closeSearch() {
|
||||||
isSearchActive = false;
|
isSearchActive = false;
|
||||||
toolbar.getMenu().findItem(getResourceIdentifier(
|
toolbar.getMenu().findItem(getResourceIdentifier(
|
||||||
"action_search", "id"))
|
"action_search", "id")).setVisible(true);
|
||||||
.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
|
|
||||||
? "yt_outline_search_white_24"
|
|
||||||
: "yt_outline_search_black_24",
|
|
||||||
"drawable")
|
|
||||||
).setVisible(true);
|
|
||||||
toolbar.setTitle(originalTitle);
|
toolbar.setTitle(originalTitle);
|
||||||
searchContainer.setVisibility(View.GONE);
|
searchContainer.setVisibility(View.GONE);
|
||||||
searchView.setQuery("", false);
|
searchView.setQuery("", false);
|
||||||
@@ -331,6 +342,19 @@ public class SearchViewController {
|
|||||||
imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
|
imm.hideSoftInputFromWindow(searchView.getWindowToken(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean handleBackPress() {
|
||||||
|
if (LicenseActivityHook.searchViewController != null
|
||||||
|
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||||
|
LicenseActivityHook.searchViewController.closeSearch();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSearchActive() {
|
||||||
|
return isSearchActive;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom ArrayAdapter for search history.
|
* Custom ArrayAdapter for search history.
|
||||||
*/
|
*/
|
||||||
@@ -365,13 +389,22 @@ public class SearchViewController {
|
|||||||
|
|
||||||
// Set long click listener for deletion confirmation.
|
// Set long click listener for deletion confirmation.
|
||||||
convertView.setOnLongClickListener(v -> {
|
convertView.setOnLongClickListener(v -> {
|
||||||
new AlertDialog.Builder(activity)
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
.setTitle(query)
|
activity,
|
||||||
.setMessage(str("revanced_settings_search_remove_message"))
|
query, // Title.
|
||||||
.setPositiveButton(android.R.string.ok,
|
str("revanced_settings_search_remove_message"), // Message.
|
||||||
(dialog, which) -> removeSearchQuery(query))
|
null, // No EditText.
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
null, // OK button text.
|
||||||
.show();
|
() -> removeSearchQuery(query), // OK button action.
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
null, // No Neutral button text.
|
||||||
|
() -> {}, // Neutral button action (dismiss only).
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
Dialog dialog = dialogPair.first;
|
||||||
|
dialog.setCancelable(true); // Allow dismissal via back button.
|
||||||
|
dialog.show(); // Show the dialog.
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,10 @@ import static java.lang.Boolean.TRUE;
|
|||||||
import static app.revanced.extension.shared.settings.Setting.Availability;
|
import static app.revanced.extension.shared.settings.Setting.Availability;
|
||||||
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
|
import static app.revanced.extension.shared.settings.Setting.parentsAll;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
|
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
|
||||||
|
import static app.revanced.extension.youtube.patches.ChangeHeaderPatch.HeaderLogo;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.ChangeStartPageTypeAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
|
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
|
||||||
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
|
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
|
||||||
@@ -21,14 +23,13 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerT
|
|||||||
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
||||||
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
|
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
|
||||||
|
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
|
||||||
|
import static app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController.SponsorBlockDuration;
|
||||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
|
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
|
||||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
|
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
|
||||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
|
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
|
|
||||||
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
@@ -40,12 +41,14 @@ import app.revanced.extension.shared.settings.IntegerSetting;
|
|||||||
import app.revanced.extension.shared.settings.LongSetting;
|
import app.revanced.extension.shared.settings.LongSetting;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import app.revanced.extension.shared.settings.StringSetting;
|
import app.revanced.extension.shared.settings.StringSetting;
|
||||||
|
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
|
||||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
|
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
|
||||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
|
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
|
||||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
|
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
|
||||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
|
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
|
||||||
import app.revanced.extension.youtube.patches.MiniplayerPatch;
|
import app.revanced.extension.youtube.patches.MiniplayerPatch;
|
||||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
||||||
|
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
|
||||||
|
|
||||||
public class Settings extends BaseSettings {
|
public class Settings extends BaseSettings {
|
||||||
// Video
|
// Video
|
||||||
@@ -55,32 +58,37 @@ public class Settings extends BaseSettings {
|
|||||||
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_shorts_quality_default_wifi", -2, true);
|
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_shorts_quality_default_wifi", -2, true);
|
||||||
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_shorts_quality_default_mobile", -2, true);
|
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_shorts_quality_default_mobile", -2, true);
|
||||||
public static final BooleanSetting REMEMBER_SHORTS_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_shorts_quality_last_selected", FALSE);
|
public static final BooleanSetting REMEMBER_SHORTS_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_shorts_quality_last_selected", FALSE);
|
||||||
|
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_video_quality_last_selected_toast", TRUE, false,
|
||||||
|
parentsAny(REMEMBER_VIDEO_QUALITY_LAST_SELECTED, REMEMBER_SHORTS_QUALITY_LAST_SELECTED));
|
||||||
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
|
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
|
||||||
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
||||||
|
|
||||||
// Speed
|
// Speed
|
||||||
public static final FloatSetting SPEED_TAP_AND_HOLD = new FloatSetting("revanced_speed_tap_and_hold", 2.0f, true);
|
public static final FloatSetting SPEED_TAP_AND_HOLD = new FloatSetting("revanced_speed_tap_and_hold", 2.0f, true);
|
||||||
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE);
|
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE);
|
||||||
|
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE, false,
|
||||||
|
parent(REMEMBER_PLAYBACK_SPEED_LAST_SELECTED));
|
||||||
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
|
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
|
||||||
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
||||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||||
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
|
"0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability());
|
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability());
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
|
public static final BooleanSetting HIDE_CREATOR_STORE_SHELF = new BooleanSetting("revanced_hide_creator_store_shelf", TRUE);
|
||||||
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
|
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
|
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
|
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
|
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
|
||||||
public static final BooleanSetting HIDE_HIDE_LATEST_POSTS = new BooleanSetting("revanced_hide_latest_posts_ads", TRUE);
|
public static final BooleanSetting HIDE_LATEST_POSTS = new BooleanSetting("revanced_hide_latest_posts", TRUE);
|
||||||
public static final BooleanSetting HIDE_MERCHANDISE_BANNERS = new BooleanSetting("revanced_hide_merchandise_banners", TRUE);
|
public static final BooleanSetting HIDE_MERCHANDISE_BANNERS = new BooleanSetting("revanced_hide_merchandise_banners", TRUE);
|
||||||
public static final BooleanSetting HIDE_PAID_PROMOTION_LABEL = new BooleanSetting("revanced_hide_paid_promotion_label", TRUE);
|
public static final BooleanSetting HIDE_PAID_PROMOTION_LABEL = new BooleanSetting("revanced_hide_paid_promotion_label", TRUE);
|
||||||
public static final BooleanSetting HIDE_PLAYER_STORE_SHELF = new BooleanSetting("revanced_hide_player_store_shelf", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_PRODUCTS_BANNER = new BooleanSetting("revanced_hide_products_banner", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_SELF_SPONSOR = new BooleanSetting("revanced_hide_self_sponsor_ads", TRUE);
|
public static final BooleanSetting HIDE_SELF_SPONSOR = new BooleanSetting("revanced_hide_self_sponsor_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHOPPING_LINKS = new BooleanSetting("revanced_hide_shopping_links", TRUE);
|
public static final BooleanSetting HIDE_SHOPPING_LINKS = new BooleanSetting("revanced_hide_shopping_links", TRUE);
|
||||||
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_hide_video_ads", TRUE, true);
|
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_hide_video_ads", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_VISIT_STORE_BUTTON = new BooleanSetting("revanced_hide_visit_store_button", TRUE);
|
public static final BooleanSetting HIDE_VIEW_PRODUCTS_BANNER = new BooleanSetting("revanced_hide_view_products_banner", TRUE);
|
||||||
public static final BooleanSetting HIDE_WEB_SEARCH_RESULTS = new BooleanSetting("revanced_hide_web_search_results", TRUE);
|
public static final BooleanSetting HIDE_WEB_SEARCH_RESULTS = new BooleanSetting("revanced_hide_web_search_results", TRUE);
|
||||||
|
|
||||||
// Feed
|
// Feed
|
||||||
@@ -91,22 +99,23 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_COMPACT_BANNER = new BooleanSetting("revanced_hide_compact_banner", TRUE);
|
public static final BooleanSetting HIDE_COMPACT_BANNER = new BooleanSetting("revanced_hide_compact_banner", TRUE);
|
||||||
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
|
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_DOODLES = new BooleanSetting("revanced_hide_doodles", FALSE, true, "revanced_hide_doodles_user_dialog_message");
|
public static final BooleanSetting HIDE_DOODLES = new BooleanSetting("revanced_hide_doodles", FALSE, true, "revanced_hide_doodles_user_dialog_message");
|
||||||
public static final BooleanSetting HIDE_EXPANDABLE_CHIP = new BooleanSetting("revanced_hide_expandable_chip", TRUE);
|
public static final BooleanSetting HIDE_EXPANDABLE_CARD = new BooleanSetting("revanced_hide_expandable_card", TRUE);
|
||||||
public static final BooleanSetting HIDE_FEED_SURVEY = new BooleanSetting("revanced_hide_feed_survey", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_FEED = new BooleanSetting("revanced_hide_filter_bar_feed_in_feed", FALSE, true);
|
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_FEED = new BooleanSetting("revanced_hide_filter_bar_feed_in_feed", FALSE, true);
|
||||||
|
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_HISTORY = new BooleanSetting("revanced_hide_filter_bar_feed_in_history", FALSE);
|
||||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS = new BooleanSetting("revanced_hide_filter_bar_feed_in_related_videos", FALSE, true);
|
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS = new BooleanSetting("revanced_hide_filter_bar_feed_in_related_videos", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_SEARCH = new BooleanSetting("revanced_hide_filter_bar_feed_in_search", FALSE, true);
|
public static final BooleanSetting HIDE_FILTER_BAR_FEED_IN_SEARCH = new BooleanSetting("revanced_hide_filter_bar_feed_in_search", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_FLOATING_MICROPHONE_BUTTON = new BooleanSetting("revanced_hide_floating_microphone_button", TRUE, true);
|
public static final BooleanSetting HIDE_FLOATING_MICROPHONE_BUTTON = new BooleanSetting("revanced_hide_floating_microphone_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_FOR_YOU_SHELF = new BooleanSetting("revanced_hide_for_you_shelf", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_HORIZONTAL_SHELVES = new BooleanSetting("revanced_hide_horizontal_shelves", TRUE);
|
public static final BooleanSetting HIDE_HORIZONTAL_SHELVES = new BooleanSetting("revanced_hide_horizontal_shelves", TRUE);
|
||||||
public static final BooleanSetting HIDE_IMAGE_SHELF = new BooleanSetting("revanced_hide_image_shelf", TRUE);
|
public static final BooleanSetting HIDE_IMAGE_SHELF = new BooleanSetting("revanced_hide_image_shelf", TRUE);
|
||||||
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
||||||
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
|
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
|
||||||
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
||||||
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_search_result_recommendation_labels", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||||
|
public static final BooleanSetting HIDE_SURVEYS = new BooleanSetting("revanced_hide_surveys", TRUE);
|
||||||
public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
|
public static final BooleanSetting HIDE_TICKET_SHELF = new BooleanSetting("revanced_hide_ticket_shelf", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_VIDEO_RECOMMENDATION_LABELS = new BooleanSetting("revanced_hide_video_recommendation_labels", TRUE);
|
||||||
|
|
||||||
// Alternative thumbnails
|
// Alternative thumbnails
|
||||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
||||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_SUBSCRIPTIONS = new EnumSetting<>("revanced_alt_thumbnail_subscription", ThumbnailOption.ORIGINAL);
|
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_SUBSCRIPTIONS = new EnumSetting<>("revanced_alt_thumbnail_subscription", ThumbnailOption.ORIGINAL);
|
||||||
@@ -118,6 +127,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting ALT_THUMBNAIL_DEARROW_CONNECTION_TOAST = new BooleanSetting("revanced_alt_thumbnail_dearrow_connection_toast", TRUE, new DeArrowAvailability());
|
public static final BooleanSetting ALT_THUMBNAIL_DEARROW_CONNECTION_TOAST = new BooleanSetting("revanced_alt_thumbnail_dearrow_connection_toast", TRUE, new DeArrowAvailability());
|
||||||
public static final EnumSetting<ThumbnailStillTime> ALT_THUMBNAIL_STILLS_TIME = new EnumSetting<>("revanced_alt_thumbnail_stills_time", ThumbnailStillTime.MIDDLE, new StillImagesAvailability());
|
public static final EnumSetting<ThumbnailStillTime> ALT_THUMBNAIL_STILLS_TIME = new EnumSetting<>("revanced_alt_thumbnail_stills_time", ThumbnailStillTime.MIDDLE, new StillImagesAvailability());
|
||||||
public static final BooleanSetting ALT_THUMBNAIL_STILLS_FAST = new BooleanSetting("revanced_alt_thumbnail_stills_fast", FALSE, new StillImagesAvailability());
|
public static final BooleanSetting ALT_THUMBNAIL_STILLS_FAST = new BooleanSetting("revanced_alt_thumbnail_stills_fast", FALSE, new StillImagesAvailability());
|
||||||
|
|
||||||
// Hide keyword content
|
// Hide keyword content
|
||||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_HOME = new BooleanSetting("revanced_hide_keyword_content_home", FALSE);
|
public static final BooleanSetting HIDE_KEYWORD_CONTENT_HOME = new BooleanSetting("revanced_hide_keyword_content_home", FALSE);
|
||||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_keyword_content_subscriptions", FALSE);
|
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_keyword_content_subscriptions", FALSE);
|
||||||
@@ -125,10 +135,18 @@ public class Settings extends BaseSettings {
|
|||||||
public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
|
public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
|
||||||
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
||||||
|
|
||||||
|
// Channel page
|
||||||
|
public static final BooleanSetting HIDE_FOR_YOU_SHELF = new BooleanSetting("revanced_hide_for_you_shelf", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_LINKS_PREVIEW = new BooleanSetting("revanced_hide_links_preview", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_MEMBERS_SHELF = new BooleanSetting("revanced_hide_members_shelf", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_VISIT_COMMUNITY_BUTTON = new BooleanSetting("revanced_hide_visit_community_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_VISIT_STORE_BUTTON = new BooleanSetting("revanced_hide_visit_store_button", TRUE);
|
||||||
|
|
||||||
// Player
|
// Player
|
||||||
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
||||||
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
||||||
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
|
public static final BooleanSetting DISABLE_AUTO_CAPTIONS = new BooleanSetting("revanced_disable_auto_captions", FALSE, true);
|
||||||
|
public static final BooleanSetting DISABLE_CHAPTER_SKIP_DOUBLE_TAP = new BooleanSetting("revanced_disable_chapter_skip_double_tap", FALSE);
|
||||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||||
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
||||||
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
||||||
@@ -136,19 +154,17 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
|
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
|
||||||
public static final BooleanSetting HIDE_CHANNEL_MEMBER_SHELF = new BooleanSetting("revanced_hide_channel_member_shelf", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_community_guidelines", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
|
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
|
||||||
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
|
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
|
||||||
public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true);
|
public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_RELATED_VIDEO_OVERLAY = new BooleanSetting("revanced_hide_related_video_overlay", FALSE, true);
|
|
||||||
public static final BooleanSetting HIDE_HIDE_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_channel_guidelines", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
|
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
|
||||||
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
|
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_PLAYER_CONTROL_BUTTONS_BACKGROUND = new BooleanSetting("revanced_hide_player_control_buttons_background", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS = new BooleanSetting("revanced_hide_player_previous_next_buttons", FALSE, true);
|
public static final BooleanSetting HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS = new BooleanSetting("revanced_hide_player_previous_next_buttons", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_QUICK_ACTIONS = new BooleanSetting("revanced_hide_quick_actions", FALSE);
|
public static final BooleanSetting HIDE_QUICK_ACTIONS = new BooleanSetting("revanced_hide_quick_actions", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_RELATED_VIDEOS_OVERLAY = new BooleanSetting("revanced_hide_related_videos_overlay", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE);
|
public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE);
|
||||||
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
|
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
|
||||||
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
|
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
|
||||||
@@ -157,6 +173,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
|
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
|
||||||
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
|
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
|
||||||
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
|
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
|
||||||
|
|
||||||
// Miniplayer
|
// Miniplayer
|
||||||
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
|
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
|
||||||
private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
|
private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
|
||||||
@@ -169,20 +186,25 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
|
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
|
||||||
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
|
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
|
||||||
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
||||||
|
|
||||||
// External downloader
|
// External downloader
|
||||||
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
||||||
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
||||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||||
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||||
|
|
||||||
// Comments
|
// Comments
|
||||||
public static final BooleanSetting HIDE_COMMENTS_AI_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_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_AI_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_summary", FALSE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_COMMENTS_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_comments_channel_guidelines", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_COMMENTS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_comments_community_guidelines", TRUE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE);
|
public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comments_timestamp_and_emoji_buttons", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
|
public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_BUTTON = new BooleanSetting("revanced_hide_comments_timestamp_button", FALSE);
|
||||||
|
|
||||||
// Description
|
// Description
|
||||||
public static final BooleanSetting HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION = new BooleanSetting("revanced_hide_ai_generated_video_summary_section", FALSE);
|
public static final BooleanSetting HIDE_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_ASK_SECTION = new BooleanSetting("revanced_hide_ask_section", FALSE);
|
||||||
@@ -193,17 +215,20 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_KEY_CONCEPTS_SECTION = new BooleanSetting("revanced_hide_key_concepts_section", FALSE);
|
public static final BooleanSetting HIDE_KEY_CONCEPTS_SECTION = new BooleanSetting("revanced_hide_key_concepts_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
|
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
|
||||||
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
|
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
|
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_PLAYLIST_BUTTON = new BooleanSetting("revanced_hide_playlist_button", FALSE);
|
|
||||||
public static final BooleanSetting HIDE_REMIX_BUTTON = new BooleanSetting("revanced_hide_remix_button", TRUE);
|
public static final BooleanSetting HIDE_REMIX_BUTTON = new BooleanSetting("revanced_hide_remix_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
|
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SAVE_BUTTON = new BooleanSetting("revanced_hide_save_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE);
|
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_STOP_ADS_BUTTON = new BooleanSetting("revanced_hide_stop_ads_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", TRUE);
|
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_ASK_BUTTON = new BooleanSetting("revanced_hide_ask_button", FALSE);
|
|
||||||
// Player flyout menu items
|
// Player flyout menu items
|
||||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_ADDITIONAL_SETTINGS = new BooleanSetting("revanced_hide_player_flyout_additional_settings", FALSE);
|
public static final BooleanSetting HIDE_PLAYER_FLYOUT_ADDITIONAL_SETTINGS = new BooleanSetting("revanced_hide_player_flyout_additional_settings", FALSE);
|
||||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_AMBIENT_MODE = new BooleanSetting("revanced_hide_player_flyout_ambient_mode", FALSE);
|
public static final BooleanSetting HIDE_PLAYER_FLYOUT_AMBIENT_MODE = new BooleanSetting("revanced_hide_player_flyout_ambient_mode", FALSE);
|
||||||
@@ -226,6 +251,9 @@ public class Settings extends BaseSettings {
|
|||||||
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
|
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
|
||||||
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
||||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
||||||
|
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("revanced_splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, true);
|
||||||
|
public static final EnumSetting<HeaderLogo> HEADER_LOGO = new EnumSetting<>("revanced_header_logo", HeaderLogo.DEFAULT, true);
|
||||||
|
|
||||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
||||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
||||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
||||||
@@ -234,9 +262,11 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
|
public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
|
||||||
new ChangeStartPageTypeAvailability());
|
new ChangeStartPageTypeAvailability());
|
||||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION));
|
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION));
|
||||||
|
|
||||||
// Custom filter
|
// Custom filter
|
||||||
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
||||||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||||
|
|
||||||
// Navigation buttons
|
// Navigation buttons
|
||||||
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true);
|
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true);
|
||||||
@@ -259,7 +289,9 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_EFFECT_BUTTON = new BooleanSetting("revanced_hide_shorts_effect_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_GREEN_SCREEN_BUTTON = new BooleanSetting("revanced_hide_shorts_green_screen_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_GREEN_SCREEN_BUTTON = new BooleanSetting("revanced_hide_shorts_green_screen_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_NEW_POSTS_BUTTON = new BooleanSetting("revanced_hide_shorts_new_posts_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_HISTORY = new BooleanSetting("revanced_hide_shorts_history", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_HISTORY = new BooleanSetting("revanced_hide_shorts_history", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
|
||||||
@@ -270,6 +302,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_SHORTS_LOCATION_LABEL = new BooleanSetting("revanced_hide_shorts_location_label", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_LOCATION_LABEL = new BooleanSetting("revanced_hide_shorts_location_label", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_NAVIGATION_BAR = new BooleanSetting("revanced_hide_shorts_navigation_bar", FALSE, true);
|
public static final BooleanSetting HIDE_SHORTS_NAVIGATION_BAR = new BooleanSetting("revanced_hide_shorts_navigation_bar", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_SHORTS_PAUSED_OVERLAY_BUTTONS = new BooleanSetting("revanced_hide_shorts_paused_overlay_buttons", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_PAUSED_OVERLAY_BUTTONS = new BooleanSetting("revanced_hide_shorts_paused_overlay_buttons", FALSE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_shorts_preview_comment", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_REMIX_BUTTON = new BooleanSetting("revanced_hide_shorts_remix_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_REMIX_BUTTON = new BooleanSetting("revanced_hide_shorts_remix_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_SAVE_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_save_sound_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_SAVE_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_save_sound_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE);
|
||||||
@@ -284,6 +317,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_SHORTS_SUPER_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_super_thanks_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_SUPER_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_super_thanks_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_TAGGED_PRODUCTS = new BooleanSetting("revanced_hide_shorts_tagged_products", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_TAGGED_PRODUCTS = new BooleanSetting("revanced_hide_shorts_tagged_products", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_UPCOMING_BUTTON = new BooleanSetting("revanced_hide_shorts_upcoming_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_UPCOMING_BUTTON = new BooleanSetting("revanced_hide_shorts_upcoming_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_USE_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_use_sound_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_USE_TEMPLATE_BUTTON = new BooleanSetting("revanced_hide_shorts_use_template_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_USE_TEMPLATE_BUTTON = new BooleanSetting("revanced_hide_shorts_use_template_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_VIDEO_TITLE = new BooleanSetting("revanced_hide_shorts_video_title", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_VIDEO_TITLE = new BooleanSetting("revanced_hide_shorts_video_title", FALSE);
|
||||||
public static final BooleanSetting SHORTS_AUTOPLAY = new BooleanSetting("revanced_shorts_autoplay", FALSE);
|
public static final BooleanSetting SHORTS_AUTOPLAY = new BooleanSetting("revanced_shorts_autoplay", FALSE);
|
||||||
@@ -303,7 +337,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final StringSetting SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_primary", "#FF0033", true, parent(SEEKBAR_CUSTOM_COLOR));
|
public static final StringSetting SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_primary", "#FF0033", true, parent(SEEKBAR_CUSTOM_COLOR));
|
||||||
public static final StringSetting SEEKBAR_CUSTOM_COLOR_ACCENT = new StringSetting("revanced_seekbar_custom_color_accent", "#FF2791", true, parent(SEEKBAR_CUSTOM_COLOR));
|
public static final StringSetting SEEKBAR_CUSTOM_COLOR_ACCENT = new StringSetting("revanced_seekbar_custom_color_accent", "#FF2791", true, parent(SEEKBAR_CUSTOM_COLOR));
|
||||||
|
|
||||||
// Misc
|
// Miscellaneous
|
||||||
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
||||||
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
|
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
|
||||||
public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE);
|
public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE);
|
||||||
@@ -368,7 +402,11 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting SB_SQUARE_LAYOUT = new BooleanSetting("sb_square_layout", FALSE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_SQUARE_LAYOUT = new BooleanSetting("sb_square_layout", FALSE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_COMPACT_SKIP_BUTTON = new BooleanSetting("sb_compact_skip_button", FALSE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_COMPACT_SKIP_BUTTON = new BooleanSetting("sb_compact_skip_button", FALSE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_AUTO_HIDE_SKIP_BUTTON = new BooleanSetting("sb_auto_hide_skip_button", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_AUTO_HIDE_SKIP_BUTTON = new BooleanSetting("sb_auto_hide_skip_button", TRUE, parent(SB_ENABLED));
|
||||||
|
public static final EnumSetting<SponsorBlockDuration> SB_AUTO_HIDE_SKIP_BUTTON_DURATION = new EnumSetting<>("sb_auto_hide_skip_button_duration",
|
||||||
|
SponsorBlockDuration.FOUR_SECONDS, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE, parent(SB_ENABLED));
|
||||||
|
public static final EnumSetting<SponsorBlockDuration> SB_TOAST_ON_SKIP_DURATION = new EnumSetting<>("sb_toast_on_skip_duration",
|
||||||
|
SponsorBlockDuration.FOUR_SECONDS, parentsAll(SB_ENABLED, SB_TOAST_ON_SKIP));
|
||||||
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", TRUE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_TRACK_SKIP_COUNT = new BooleanSetting("sb_track_skip_count", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_TRACK_SKIP_COUNT = new BooleanSetting("sb_track_skip_count", TRUE, parent(SB_ENABLED));
|
||||||
public static final FloatSetting SB_SEGMENT_MIN_DURATION = new FloatSetting("sb_min_segment_duration", 0F, parent(SB_ENABLED));
|
public static final FloatSetting SB_SEGMENT_MIN_DURATION = new FloatSetting("sb_min_segment_duration", 0F, parent(SB_ENABLED));
|
||||||
|
|||||||
@@ -3,17 +3,18 @@ package app.revanced.extension.youtube.settings.preference;
|
|||||||
import static app.revanced.extension.shared.StringRef.sf;
|
import static app.revanced.extension.shared.StringRef.sf;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.ListPreference;
|
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||||
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* A custom ListPreference that uses a styled custom dialog with a custom checkmark indicator.
|
||||||
* Custom video speeds used by {@link CustomPlaybackSpeedPatch}.
|
* Custom video speeds used by {@link CustomPlaybackSpeedPatch}.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
public final class CustomVideoSpeedListPreference extends ListPreference {
|
public final class CustomVideoSpeedListPreference extends CustomDialogListPreference {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize a settings preference list with the available playback speeds.
|
* Initialize a settings preference list with the available playback speeds.
|
||||||
@@ -59,4 +60,5 @@ public final class CustomVideoSpeedListPreference extends ListPreference {
|
|||||||
public CustomVideoSpeedListPreference(Context context) {
|
public CustomVideoSpeedListPreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ import android.preference.SwitchPreference;
|
|||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.style.BackgroundColorSpan;
|
import android.text.style.BackgroundColorSpan;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
@@ -40,7 +42,6 @@ import app.revanced.extension.shared.Utils;
|
|||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
|
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
|
||||||
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
|
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
|
||||||
import app.revanced.extension.youtube.settings.LicenseActivityHook;
|
import app.revanced.extension.youtube.settings.LicenseActivityHook;
|
||||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
|
||||||
|
|
||||||
@@ -71,11 +72,27 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
|
|
||||||
@SuppressLint("UseCompatLoadingForDrawables")
|
@SuppressLint("UseCompatLoadingForDrawables")
|
||||||
public static Drawable getBackButtonDrawable() {
|
public static Drawable getBackButtonDrawable() {
|
||||||
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
|
final int backButtonResource = getResourceIdentifier("revanced_settings_toolbar_arrow_left", "drawable");
|
||||||
? "yt_outline_arrow_left_white_24"
|
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
|
||||||
: "yt_outline_arrow_left_black_24",
|
drawable.setTint(Utils.getAppForegroundColor());
|
||||||
"drawable");
|
return drawable;
|
||||||
return Utils.getContext().getResources().getDrawable(backButtonResource);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the system navigation bar color for the activity.
|
||||||
|
* Applies the background color obtained from {@link Utils#getAppBackgroundColor()} 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(Utils.getAppBackgroundColor());
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
window.setNavigationBarContrastEnforced(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,9 +218,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
// Set icon for the placeholder preference.
|
// Set icon for the placeholder preference.
|
||||||
noResultsPreference.setLayoutResource(getResourceIdentifier(
|
noResultsPreference.setLayoutResource(getResourceIdentifier(
|
||||||
"revanced_preference_with_icon_no_search_result", "layout"));
|
"revanced_preference_with_icon_no_search_result", "layout"));
|
||||||
noResultsPreference.setIcon(getResourceIdentifier(
|
noResultsPreference.setIcon(getResourceIdentifier("revanced_settings_search_icon", "drawable"));
|
||||||
ThemeHelper.isDarkTheme() ? "yt_outline_search_white_24" : "yt_outline_search_black_24",
|
|
||||||
"drawable"));
|
|
||||||
preferenceScreen.addPreference(noResultsPreference);
|
preferenceScreen.addPreference(noResultsPreference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,7 +241,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
.getParent();
|
.getParent();
|
||||||
|
|
||||||
// Fix the system navigation bar color for submenus.
|
// Fix the system navigation bar color for submenus.
|
||||||
ThemeHelper.setNavigationBarColor(preferenceScreenDialog.getWindow());
|
setNavigationBarColor(preferenceScreenDialog.getWindow());
|
||||||
|
|
||||||
// Fix edge-to-edge screen with Android 15 and YT 19.45+
|
// 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
|
// https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets
|
||||||
@@ -234,7 +249,15 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
|
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||||
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
|
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
|
||||||
Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
|
Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
|
||||||
v.setPadding(0, statusInsets.top, 0, navInsets.bottom);
|
Insets cutoutInsets = insets.getInsets(WindowInsets.Type.displayCutout());
|
||||||
|
|
||||||
|
// Apply padding for display cutout in landscape.
|
||||||
|
int leftPadding = cutoutInsets.left;
|
||||||
|
int rightPadding = cutoutInsets.right;
|
||||||
|
int topPadding = statusInsets.top;
|
||||||
|
int bottomPadding = navInsets.bottom;
|
||||||
|
|
||||||
|
v.setPadding(leftPadding, topPadding, rightPadding, bottomPadding);
|
||||||
return insets;
|
return insets;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -250,11 +273,17 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
TextView toolbarTextView = Utils.getChildView(toolbar,
|
TextView toolbarTextView = Utils.getChildView(toolbar,
|
||||||
true, TextView.class::isInstance);
|
true, TextView.class::isInstance);
|
||||||
if (toolbarTextView != null) {
|
if (toolbarTextView != null) {
|
||||||
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor());
|
toolbarTextView.setTextColor(Utils.getAppForegroundColor());
|
||||||
|
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
LicenseActivityHook.setToolbarLayoutParams(toolbar);
|
LicenseActivityHook.setToolbarLayoutParams(toolbar);
|
||||||
|
|
||||||
|
if (LicenseActivityHook.searchViewController != null
|
||||||
|
&& LicenseActivityHook.searchViewController.isSearchActive()) {
|
||||||
|
toolbar.post(() -> LicenseActivityHook.searchViewController.closeSearch());
|
||||||
|
}
|
||||||
|
|
||||||
rootView.addView(toolbar, 0);
|
rootView.addView(toolbar, 0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -304,10 +333,10 @@ class AbstractPreferenceSearchData<T extends Preference> {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
final int baseColor = ThemeHelper.getBackgroundColor();
|
final int baseColor = Utils.getAppBackgroundColor();
|
||||||
final int adjustedColor = ThemeHelper.isDarkTheme()
|
final int adjustedColor = Utils.isDarkModeEnabled()
|
||||||
? ThemeHelper.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme.
|
? Utils.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme.
|
||||||
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
|
: Utils.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
|
||||||
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor);
|
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor);
|
||||||
|
|
||||||
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
|
SpannableStringBuilder spannable = new SpannableStringBuilder(text);
|
||||||
|
|||||||
@@ -1,32 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.settings.preference;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public class ReVancedYouTubeAboutPreference extends ReVancedAboutPreference {
|
|
||||||
|
|
||||||
public int getLightColor() {
|
|
||||||
return ThemeHelper.getLightThemeColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getDarkColor() {
|
|
||||||
return ThemeHelper.getDarkThemeColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
public ReVancedYouTubeAboutPreference(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +1,37 @@
|
|||||||
package app.revanced.extension.youtube.sponsorblock;
|
package app.revanced.extension.youtube.sponsorblock;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
|
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
|
import android.graphics.drawable.shapes.RoundRectShape;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import android.util.Range;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
@@ -23,6 +44,7 @@ import app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory;
|
|||||||
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
|
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
|
||||||
import app.revanced.extension.youtube.sponsorblock.requests.SBRequester;
|
import app.revanced.extension.youtube.sponsorblock.requests.SBRequester;
|
||||||
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController;
|
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController;
|
||||||
|
import kotlin.Unit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles showing, scheduling, and skipping of all {@link SponsorSegment} for the current video.
|
* Handles showing, scheduling, and skipping of all {@link SponsorSegment} for the current video.
|
||||||
@@ -30,20 +52,37 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController
|
|||||||
* Class is not thread safe. All methods must be called on the main thread unless otherwise specified.
|
* Class is not thread safe. All methods must be called on the main thread unless otherwise specified.
|
||||||
*/
|
*/
|
||||||
public class SegmentPlaybackController {
|
public class SegmentPlaybackController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Length of time to show a skip button for a highlight segment,
|
* Enum for configurable durations (1 to 10 seconds) for skip button and toast display.
|
||||||
* or a regular segment if {@link Settings#SB_AUTO_HIDE_SKIP_BUTTON} is enabled.
|
|
||||||
*
|
|
||||||
* Effectively this value is rounded up to the next second.
|
|
||||||
*/
|
*/
|
||||||
private static final long DURATION_TO_SHOW_SKIP_BUTTON = 3800;
|
public enum SponsorBlockDuration {
|
||||||
|
ONE_SECOND(1),
|
||||||
|
TWO_SECONDS(2),
|
||||||
|
THREE_SECONDS(3),
|
||||||
|
FOUR_SECONDS(4),
|
||||||
|
FIVE_SECONDS(5),
|
||||||
|
SIX_SECONDS(6),
|
||||||
|
SEVEN_SECONDS(7),
|
||||||
|
EIGHT_SECONDS(8),
|
||||||
|
NINE_SECONDS(9),
|
||||||
|
TEN_SECONDS(10);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duration, minus 200ms to adjust for exclusive end time checking in scheduled show/hides.
|
||||||
|
*/
|
||||||
|
private final long adjustedDuration;
|
||||||
|
|
||||||
|
SponsorBlockDuration(int seconds) {
|
||||||
|
adjustedDuration = seconds * 1000L - 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Highlight segments have zero length as they are a point in time.
|
* Highlight segments have zero length as they are a point in time.
|
||||||
* Draw them on screen using a fixed width bar.
|
* Draw them on screen using a fixed width bar.
|
||||||
* Value is independent of device dpi.
|
|
||||||
*/
|
*/
|
||||||
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = 7;
|
private static final int HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH = dipToPixels(7);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String currentVideoId;
|
private static String currentVideoId;
|
||||||
@@ -59,7 +98,7 @@ public class SegmentPlaybackController {
|
|||||||
/**
|
/**
|
||||||
* Because loading can take time, show the skip to highlight for a few seconds after the segments load.
|
* Because loading can take time, show the skip to highlight for a few seconds after the segments load.
|
||||||
* This is the system time (in milliseconds) to no longer show the initial display skip to highlight.
|
* This is the system time (in milliseconds) to no longer show the initial display skip to highlight.
|
||||||
* Value will be zero if no highlight segment exists, or if the system time to show the highlight has passed.
|
* Value is zero if no highlight segment exists, or if the system time to show the highlight has passed.
|
||||||
*/
|
*/
|
||||||
private static long highlightSegmentInitialShowEndTime;
|
private static long highlightSegmentInitialShowEndTime;
|
||||||
|
|
||||||
@@ -70,7 +109,7 @@ public class SegmentPlaybackController {
|
|||||||
private static SponsorSegment segmentCurrentlyPlaying;
|
private static SponsorSegment segmentCurrentlyPlaying;
|
||||||
/**
|
/**
|
||||||
* Currently playing manual skip segment that is scheduled to hide.
|
* Currently playing manual skip segment that is scheduled to hide.
|
||||||
* This will always be NULL or equal to {@link #segmentCurrentlyPlaying}.
|
* This is always NULL or equal to {@link #segmentCurrentlyPlaying}.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static SponsorSegment scheduledHideSegment;
|
private static SponsorSegment scheduledHideSegment;
|
||||||
@@ -89,31 +128,95 @@ public class SegmentPlaybackController {
|
|||||||
*/
|
*/
|
||||||
private static final List<SponsorSegment> hiddenSkipSegmentsForCurrentVideoTime = new ArrayList<>();
|
private static final List<SponsorSegment> hiddenSkipSegmentsForCurrentVideoTime = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current segments that have been auto skipped.
|
||||||
|
* If field is non null then the range will always contain the current video time.
|
||||||
|
* Range is used to prevent auto-skipping after undo.
|
||||||
|
* Android Range object has inclusive end time, unlike {@link SponsorSegment}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Range<Long> undoAutoSkipRange;
|
||||||
|
/**
|
||||||
|
* Range to undo if the toast is tapped.
|
||||||
|
* Is always null or identical to the last non null value of {@link #undoAutoSkipRange}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private static Range<Long> undoAutoSkipRangeToast;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* System time (in milliseconds) of when to hide the skip button of {@link #segmentCurrentlyPlaying}.
|
* System time (in milliseconds) of when to hide the skip button of {@link #segmentCurrentlyPlaying}.
|
||||||
* Value is zero if playback is not inside a segment ({@link #segmentCurrentlyPlaying} is null),
|
* Value is zero if playback is not inside a segment ({@link #segmentCurrentlyPlaying} is null),
|
||||||
* or if {@link Settings#SB_AUTO_HIDE_SKIP_BUTTON} is not enabled.
|
* or if {@link Settings#SB_AUTO_HIDE_SKIP_BUTTON} is not enabled.
|
||||||
*/
|
*/
|
||||||
private static long skipSegmentButtonEndTime;
|
private static long skipSegmentButtonEndTime;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String timeWithoutSegments;
|
private static String timeWithoutSegments;
|
||||||
|
|
||||||
private static int sponsorBarAbsoluteLeft;
|
private static int sponsorBarAbsoluteLeft;
|
||||||
private static int sponsorAbsoluteBarRight;
|
private static int sponsorAbsoluteBarRight;
|
||||||
private static int sponsorBarThickness;
|
private static int sponsorBarThickness;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static SponsorSegment lastSegmentSkipped;
|
||||||
|
private static long lastSegmentSkippedTime;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static SponsorSegment toastSegmentSkipped;
|
||||||
|
private static int toastNumberOfSegmentsSkipped;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last toast dialog showing on screen.
|
||||||
|
*/
|
||||||
|
private static WeakReference<Dialog> toastDialogRef = new WeakReference<>(null);
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Dismiss toast if app changes to PiP while undo skip is shown.
|
||||||
|
PlayerType.getOnChange().addObserver((PlayerType type) -> {
|
||||||
|
if (type == PlayerType.WATCH_WHILE_PICTURE_IN_PICTURE && dismissUndoToast()) {
|
||||||
|
Logger.printDebug(() -> "Dismissed undo toast as playback is PiP");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Unit.INSTANCE;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the toast was on screen and is now dismissed.
|
||||||
|
*/
|
||||||
|
private static boolean dismissUndoToast() {
|
||||||
|
Dialog toastDialog = toastDialogRef.get();
|
||||||
|
if (toastDialog != null && toastDialog.isShowing()) {
|
||||||
|
toastDialog.dismiss();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The adjusted duration to show the skip button, in milliseconds.
|
||||||
|
*/
|
||||||
|
private static long getSkipButtonDuration() {
|
||||||
|
return Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.get().adjustedDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The adjusted duration to show the skipped toast, in milliseconds.
|
||||||
|
*/
|
||||||
|
private static long getToastDuration() {
|
||||||
|
return Settings.SB_TOAST_ON_SKIP_DURATION.get().adjustedDuration;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
static SponsorSegment[] getSegments() {
|
static SponsorSegment[] getSegments() {
|
||||||
return segments;
|
return segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setSegments(@NonNull SponsorSegment[] videoSegments) {
|
private static void setSegments(SponsorSegment[] videoSegments) {
|
||||||
Arrays.sort(videoSegments);
|
Arrays.sort(videoSegments);
|
||||||
segments = videoSegments;
|
segments = videoSegments;
|
||||||
calculateTimeWithoutSegments();
|
calculateTimeWithoutSegments();
|
||||||
|
|
||||||
if (SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.SKIP_AUTOMATICALLY
|
if (SegmentCategory.HIGHLIGHT.behaviour == SKIP_AUTOMATICALLY
|
||||||
|| SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.MANUAL_SKIP) {
|
|| SegmentCategory.HIGHLIGHT.behaviour == CategoryBehaviour.MANUAL_SKIP) {
|
||||||
for (SponsorSegment segment : videoSegments) {
|
for (SponsorSegment segment : videoSegments) {
|
||||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||||
@@ -125,7 +228,7 @@ public class SegmentPlaybackController {
|
|||||||
highlightSegment = null;
|
highlightSegment = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addUnsubmittedSegment(@NonNull SponsorSegment segment) {
|
static void addUnsubmittedSegment(SponsorSegment segment) {
|
||||||
Objects.requireNonNull(segment);
|
Objects.requireNonNull(segment);
|
||||||
if (segments == null) {
|
if (segments == null) {
|
||||||
segments = new SponsorSegment[1];
|
segments = new SponsorSegment[1];
|
||||||
@@ -140,6 +243,7 @@ public class SegmentPlaybackController {
|
|||||||
if (segments == null || segments.length == 0) {
|
if (segments == null || segments.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SponsorSegment> replacement = new ArrayList<>();
|
List<SponsorSegment> replacement = new ArrayList<>();
|
||||||
for (SponsorSegment segment : segments) {
|
for (SponsorSegment segment : segments) {
|
||||||
if (segment.category != SegmentCategory.UNSUBMITTED) {
|
if (segment.category != SegmentCategory.UNSUBMITTED) {
|
||||||
@@ -156,7 +260,7 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all downloaded data.
|
* Clear all data.
|
||||||
*/
|
*/
|
||||||
private static void clearData() {
|
private static void clearData() {
|
||||||
currentVideoId = null;
|
currentVideoId = null;
|
||||||
@@ -170,6 +274,8 @@ public class SegmentPlaybackController {
|
|||||||
skipSegmentButtonEndTime = 0;
|
skipSegmentButtonEndTime = 0;
|
||||||
toastSegmentSkipped = null;
|
toastSegmentSkipped = null;
|
||||||
toastNumberOfSegmentsSkipped = 0;
|
toastNumberOfSegmentsSkipped = 0;
|
||||||
|
undoAutoSkipRange = null;
|
||||||
|
undoAutoSkipRangeToast = null;
|
||||||
hiddenSkipSegmentsForCurrentVideoTime.clear();
|
hiddenSkipSegmentsForCurrentVideoTime.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +292,7 @@ public class SegmentPlaybackController {
|
|||||||
SponsorBlockUtils.clearUnsubmittedSegmentTimes();
|
SponsorBlockUtils.clearUnsubmittedSegmentTimes();
|
||||||
Logger.printDebug(() -> "Initialized SponsorBlock");
|
Logger.printDebug(() -> "Initialized SponsorBlock");
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Failed to initialize SponsorBlock", ex);
|
Logger.printException(() -> "initialize failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,7 +309,7 @@ public class SegmentPlaybackController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PlayerType.getCurrent().isNoneOrHidden()) {
|
if (PlayerType.getCurrent().isNoneOrHidden()) {
|
||||||
Logger.printDebug(() -> "ignoring Short");
|
Logger.printDebug(() -> "Ignoring Short");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!Utils.isNetworkConnected()) {
|
if (!Utils.isNetworkConnected()) {
|
||||||
@@ -212,7 +318,7 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentVideoId = videoId;
|
currentVideoId = videoId;
|
||||||
Logger.printDebug(() -> "setCurrentVideoId: " + videoId);
|
Logger.printDebug(() -> "New video ID: " + videoId);
|
||||||
|
|
||||||
Utils.runOnBackgroundThread(() -> {
|
Utils.runOnBackgroundThread(() -> {
|
||||||
try {
|
try {
|
||||||
@@ -227,42 +333,39 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must be called off main thread
|
* Must be called off main thread.
|
||||||
*/
|
*/
|
||||||
static void executeDownloadSegments(@NonNull String videoId) {
|
static void executeDownloadSegments(String videoId) {
|
||||||
Objects.requireNonNull(videoId);
|
Objects.requireNonNull(videoId);
|
||||||
try {
|
|
||||||
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
|
||||||
|
|
||||||
Utils.runOnMainThread(()-> {
|
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
||||||
if (!videoId.equals(currentVideoId)) {
|
|
||||||
// user changed videos before get segments network call could complete
|
|
||||||
Logger.printDebug(() -> "Ignoring segments for prior video: " + videoId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setSegments(segments);
|
|
||||||
|
|
||||||
final long videoTime = VideoInformation.getVideoTime();
|
Utils.runOnMainThread(() -> {
|
||||||
if (highlightSegment != null) {
|
if (!videoId.equals(currentVideoId)) {
|
||||||
// If the current video time is before the highlight.
|
// user changed videos before get segments network call could complete
|
||||||
final long timeUntilHighlight = highlightSegment.start - videoTime;
|
Logger.printDebug(() -> "Ignoring segments for prior video: " + videoId);
|
||||||
if (timeUntilHighlight > 0) {
|
return;
|
||||||
if (highlightSegment.shouldAutoSkip()) {
|
}
|
||||||
skipSegment(highlightSegment, false);
|
setSegments(segments);
|
||||||
return;
|
|
||||||
}
|
final long videoTime = VideoInformation.getVideoTime();
|
||||||
highlightSegmentInitialShowEndTime = System.currentTimeMillis() + Math.min(
|
if (highlightSegment != null) {
|
||||||
(long) (timeUntilHighlight / VideoInformation.getPlaybackSpeed()),
|
// If the current video time is before the highlight.
|
||||||
DURATION_TO_SHOW_SKIP_BUTTON);
|
final long timeUntilHighlight = highlightSegment.start - videoTime;
|
||||||
|
if (timeUntilHighlight > 0) {
|
||||||
|
if (highlightSegment.shouldAutoSkip()) {
|
||||||
|
skipSegment(highlightSegment, false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
highlightSegmentInitialShowEndTime = System.currentTimeMillis() + Math.min(
|
||||||
|
(long) (timeUntilHighlight / VideoInformation.getPlaybackSpeed()),
|
||||||
|
getSkipButtonDuration());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check for any skips now, instead of waiting for the next update to setVideoTime()
|
// check for any skips now, instead of waiting for the next update to setVideoTime()
|
||||||
setVideoTime(videoTime);
|
setVideoTime(videoTime);
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "executeDownloadSegments failure", ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -273,8 +376,8 @@ public class SegmentPlaybackController {
|
|||||||
public static void setVideoTime(long millis) {
|
public static void setVideoTime(long millis) {
|
||||||
try {
|
try {
|
||||||
if (!Settings.SB_ENABLED.get()
|
if (!Settings.SB_ENABLED.get()
|
||||||
|| PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|
|| PlayerType.getCurrent().isNoneOrHidden() // Shorts playback.
|
||||||
|| segments == null || segments.length == 0) {
|
|| segments == null || segments.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Logger.printDebug(() -> "setVideoTime: " + millis);
|
Logger.printDebug(() -> "setVideoTime: " + millis);
|
||||||
@@ -290,7 +393,7 @@ public class SegmentPlaybackController {
|
|||||||
//
|
//
|
||||||
// To debug the stale skip logic, set this to a very large value (5000 or more)
|
// To debug the stale skip logic, set this to a very large value (5000 or more)
|
||||||
// then try manually seeking just before playback reaches a segment skip.
|
// then try manually seeking just before playback reaches a segment skip.
|
||||||
final long speedAdjustedTimeThreshold = (long)(playbackSpeed * 1200);
|
final long speedAdjustedTimeThreshold = (long) (playbackSpeed * 1200);
|
||||||
final long startTimerLookAheadThreshold = millis + speedAdjustedTimeThreshold;
|
final long startTimerLookAheadThreshold = millis + speedAdjustedTimeThreshold;
|
||||||
|
|
||||||
SponsorSegment foundSegmentCurrentlyPlaying = null;
|
SponsorSegment foundSegmentCurrentlyPlaying = null;
|
||||||
@@ -298,22 +401,24 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
for (final SponsorSegment segment : segments) {
|
for (final SponsorSegment segment : segments) {
|
||||||
if (segment.category.behaviour == CategoryBehaviour.SHOW_IN_SEEKBAR
|
if (segment.category.behaviour == CategoryBehaviour.SHOW_IN_SEEKBAR
|
||||||
|| segment.category.behaviour == CategoryBehaviour.IGNORE
|
|| segment.category.behaviour == CategoryBehaviour.IGNORE
|
||||||
|| segment.category == SegmentCategory.HIGHLIGHT) {
|
|| segment.category == SegmentCategory.HIGHLIGHT) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (segment.end <= millis) {
|
if (segment.end <= millis) {
|
||||||
continue; // past this segment
|
continue; // Past this segment.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean segmentShouldAutoSkip = shouldAutoSkipAndUndoSkipNotActive(segment, millis);
|
||||||
|
|
||||||
if (segment.start <= millis) {
|
if (segment.start <= millis) {
|
||||||
// we are in the segment!
|
// We are in the segment!
|
||||||
if (segment.shouldAutoSkip()) {
|
if (segmentShouldAutoSkip) {
|
||||||
skipSegment(segment, false);
|
skipSegment(segment, false);
|
||||||
return; // must return, as skipping causes a recursive call back into this method
|
return; // Must return, as skipping causes a recursive call back into this method.
|
||||||
}
|
}
|
||||||
|
|
||||||
// first found segment, or it's an embedded segment and fully inside the outer segment
|
// First found segment, or it's an embedded segment and fully inside the outer segment.
|
||||||
if (foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment)) {
|
if (foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment)) {
|
||||||
// If the found segment is not currently displayed, then do not show if the segment is nearly over.
|
// If the found segment is not currently displayed, then do not show if the segment is nearly over.
|
||||||
// This check prevents the skip button text from rapidly changing when multiple segments end at nearly the same time.
|
// This check prevents the skip button text from rapidly changing when multiple segments end at nearly the same time.
|
||||||
@@ -327,25 +432,27 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Keep iterating and looking. There may be an upcoming autoskip,
|
// Keep iterating and looking. There may be an upcoming autoskip,
|
||||||
// or there may be another smaller segment nested inside this segment
|
// or there may be another smaller segment nested inside this segment.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// segment is upcoming
|
// Segment is upcoming.
|
||||||
if (startTimerLookAheadThreshold < segment.start) {
|
if (startTimerLookAheadThreshold < segment.start) {
|
||||||
break; // segment is not close enough to schedule, and no segments after this are of interest
|
// Segment is not close enough to schedule, and no segments after this are of interest.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (segment.shouldAutoSkip()) { // upcoming autoskip
|
|
||||||
|
if (segmentShouldAutoSkip) {
|
||||||
foundUpcomingSegment = segment;
|
foundUpcomingSegment = segment;
|
||||||
break; // must stop here
|
break; // Must stop here.
|
||||||
}
|
}
|
||||||
|
|
||||||
// upcoming manual skip
|
// Upcoming manual skip.
|
||||||
|
|
||||||
// do not schedule upcoming segment, if it is not fully contained inside the current segment
|
// Do not schedule upcoming segment, if it is not fully contained inside the current segment.
|
||||||
if ((foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment))
|
if ((foundSegmentCurrentlyPlaying == null || foundSegmentCurrentlyPlaying.containsSegment(segment))
|
||||||
// use the most inner upcoming segment
|
// Use the most inner upcoming segment.
|
||||||
&& (foundUpcomingSegment == null || foundUpcomingSegment.containsSegment(segment))) {
|
&& (foundUpcomingSegment == null || foundUpcomingSegment.containsSegment(segment))) {
|
||||||
|
|
||||||
// Only schedule, if the segment start time is not near the end time of the current segment.
|
// Only schedule, if the segment start time is not near the end time of the current segment.
|
||||||
// This check is needed to prevent scheduled hide and show from clashing with each other.
|
// This check is needed to prevent scheduled hide and show from clashing with each other.
|
||||||
@@ -361,8 +468,8 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (highlightSegment != null) {
|
if (highlightSegment != null) {
|
||||||
if (millis < DURATION_TO_SHOW_SKIP_BUTTON || (highlightSegmentInitialShowEndTime != 0
|
if (millis < getSkipButtonDuration() || (highlightSegmentInitialShowEndTime != 0
|
||||||
&& System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) {
|
&& System.currentTimeMillis() < highlightSegmentInitialShowEndTime)) {
|
||||||
SponsorBlockViewController.showSkipHighlightButton(highlightSegment);
|
SponsorBlockViewController.showSkipHighlightButton(highlightSegment);
|
||||||
} else {
|
} else {
|
||||||
highlightSegmentInitialShowEndTime = 0;
|
highlightSegmentInitialShowEndTime = 0;
|
||||||
@@ -373,16 +480,17 @@ public class SegmentPlaybackController {
|
|||||||
if (segmentCurrentlyPlaying != foundSegmentCurrentlyPlaying) {
|
if (segmentCurrentlyPlaying != foundSegmentCurrentlyPlaying) {
|
||||||
setSegmentCurrentlyPlaying(foundSegmentCurrentlyPlaying);
|
setSegmentCurrentlyPlaying(foundSegmentCurrentlyPlaying);
|
||||||
} else if (foundSegmentCurrentlyPlaying != null
|
} else if (foundSegmentCurrentlyPlaying != null
|
||||||
&& skipSegmentButtonEndTime != 0 && skipSegmentButtonEndTime <= System.currentTimeMillis()) {
|
&& skipSegmentButtonEndTime != 0
|
||||||
|
&& skipSegmentButtonEndTime <= System.currentTimeMillis()) {
|
||||||
Logger.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying);
|
Logger.printDebug(() -> "Auto hiding skip button for segment: " + segmentCurrentlyPlaying);
|
||||||
skipSegmentButtonEndTime = 0;
|
skipSegmentButtonEndTime = 0;
|
||||||
hiddenSkipSegmentsForCurrentVideoTime.add(foundSegmentCurrentlyPlaying);
|
hiddenSkipSegmentsForCurrentVideoTime.add(foundSegmentCurrentlyPlaying);
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
}
|
}
|
||||||
|
|
||||||
// schedule a hide, only if the segment end is near
|
// Schedule a hide, but only if the segment end is near.
|
||||||
final SponsorSegment segmentToHide =
|
final SponsorSegment segmentToHide = (foundSegmentCurrentlyPlaying != null &&
|
||||||
(foundSegmentCurrentlyPlaying != null && foundSegmentCurrentlyPlaying.endIsNear(millis, speedAdjustedTimeThreshold))
|
foundSegmentCurrentlyPlaying.endIsNear(millis, speedAdjustedTimeThreshold))
|
||||||
? foundSegmentCurrentlyPlaying
|
? foundSegmentCurrentlyPlaying
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -407,7 +515,7 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
final long videoTime = VideoInformation.getVideoTime();
|
final long videoTime = VideoInformation.getVideoTime();
|
||||||
if (!segmentToHide.endIsNear(videoTime, speedAdjustedTimeThreshold)) {
|
if (!segmentToHide.endIsNear(videoTime, speedAdjustedTimeThreshold)) {
|
||||||
// current video time is not what's expected. User paused playback
|
// Current video time is not what's expected. User paused playback.
|
||||||
Logger.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide
|
Logger.printDebug(() -> "Ignoring outdated scheduled hide: " + segmentToHide
|
||||||
+ " videoInformation time: " + videoTime);
|
+ " videoInformation time: " + videoTime);
|
||||||
return;
|
return;
|
||||||
@@ -416,7 +524,7 @@ public class SegmentPlaybackController {
|
|||||||
// Need more than just hide the skip button, as this may have been an embedded segment
|
// Need more than just hide the skip button, as this may have been an embedded segment
|
||||||
// Instead call back into setVideoTime to check everything again.
|
// Instead call back into setVideoTime to check everything again.
|
||||||
// Should not use VideoInformation time as it is less accurate,
|
// Should not use VideoInformation time as it is less accurate,
|
||||||
// but this scheduled handler was scheduled precisely so we can just use the segment end time
|
// but this scheduled handler was scheduled precisely so we can just use the segment end time.
|
||||||
setSegmentCurrentlyPlaying(null);
|
setSegmentCurrentlyPlaying(null);
|
||||||
setVideoTime(segmentToHide.end);
|
setVideoTime(segmentToHide.end);
|
||||||
}, delayUntilHide);
|
}, delayUntilHide);
|
||||||
@@ -446,12 +554,12 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
final long videoTime = VideoInformation.getVideoTime();
|
final long videoTime = VideoInformation.getVideoTime();
|
||||||
if (!segmentToSkip.startIsNear(videoTime, speedAdjustedTimeThreshold)) {
|
if (!segmentToSkip.startIsNear(videoTime, speedAdjustedTimeThreshold)) {
|
||||||
// current video time is not what's expected. User paused playback
|
// Current video time is not what's expected. User paused playback.
|
||||||
Logger.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip
|
Logger.printDebug(() -> "Ignoring outdated scheduled segment: " + segmentToSkip
|
||||||
+ " videoInformation time: " + videoTime);
|
+ " videoInformation time: " + videoTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (segmentToSkip.shouldAutoSkip()) {
|
if (shouldAutoSkipAndUndoSkipNotActive(segmentToSkip, videoTime)) {
|
||||||
Logger.printDebug(() -> "Running scheduled skip segment: " + segmentToSkip);
|
Logger.printDebug(() -> "Running scheduled skip segment: " + segmentToSkip);
|
||||||
skipSegment(segmentToSkip, false);
|
skipSegment(segmentToSkip, false);
|
||||||
} else {
|
} else {
|
||||||
@@ -461,6 +569,12 @@ public class SegmentPlaybackController {
|
|||||||
}, delayUntilSkip);
|
}, delayUntilSkip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear undo range if video time is outside the segment. Must check last.
|
||||||
|
if (undoAutoSkipRange != null && !undoAutoSkipRange.contains(millis)) {
|
||||||
|
Logger.printDebug(() -> "Clearing undo range as current time is now outside range: " + undoAutoSkipRange);
|
||||||
|
undoAutoSkipRange = null;
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.printException(() -> "setVideoTime failure", e);
|
Logger.printException(() -> "setVideoTime failure", e);
|
||||||
}
|
}
|
||||||
@@ -470,14 +584,13 @@ public class SegmentPlaybackController {
|
|||||||
* Removes all previously hidden segments that are not longer contained in the given video time.
|
* Removes all previously hidden segments that are not longer contained in the given video time.
|
||||||
*/
|
*/
|
||||||
private static void updateHiddenSegments(long currentVideoTime) {
|
private static void updateHiddenSegments(long currentVideoTime) {
|
||||||
Iterator<SponsorSegment> i = hiddenSkipSegmentsForCurrentVideoTime.iterator();
|
hiddenSkipSegmentsForCurrentVideoTime.removeIf((hiddenSegment) -> {
|
||||||
while (i.hasNext()) {
|
|
||||||
SponsorSegment hiddenSegment = i.next();
|
|
||||||
if (!hiddenSegment.containsTime(currentVideoTime)) {
|
if (!hiddenSegment.containsTime(currentVideoTime)) {
|
||||||
Logger.printDebug(() -> "Resetting hide skip button: " + hiddenSegment);
|
Logger.printDebug(() -> "Resetting hide skip button: " + hiddenSegment);
|
||||||
i.remove();
|
return true;
|
||||||
}
|
}
|
||||||
}
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
|
private static void setSegmentCurrentlyPlaying(@Nullable SponsorSegment segment) {
|
||||||
@@ -488,8 +601,10 @@ public class SegmentPlaybackController {
|
|||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
segmentCurrentlyPlaying = segment;
|
segmentCurrentlyPlaying = segment;
|
||||||
skipSegmentButtonEndTime = 0;
|
skipSegmentButtonEndTime = 0;
|
||||||
|
|
||||||
if (Settings.SB_AUTO_HIDE_SKIP_BUTTON.get()) {
|
if (Settings.SB_AUTO_HIDE_SKIP_BUTTON.get()) {
|
||||||
if (hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) {
|
if (hiddenSkipSegmentsForCurrentVideoTime.contains(segment)) {
|
||||||
// Playback exited a nested segment and the outer segment skip button was previously hidden.
|
// Playback exited a nested segment and the outer segment skip button was previously hidden.
|
||||||
@@ -497,16 +612,13 @@ public class SegmentPlaybackController {
|
|||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
skipSegmentButtonEndTime = System.currentTimeMillis() + DURATION_TO_SHOW_SKIP_BUTTON;
|
skipSegmentButtonEndTime = System.currentTimeMillis() + getSkipButtonDuration();
|
||||||
}
|
}
|
||||||
Logger.printDebug(() -> "Showing segment: " + segment);
|
Logger.printDebug(() -> "Showing segment: " + segment);
|
||||||
SponsorBlockViewController.showSkipSegmentButton(segment);
|
SponsorBlockViewController.showSkipSegmentButton(segment);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SponsorSegment lastSegmentSkipped;
|
private static void skipSegment(SponsorSegment segmentToSkip, boolean userManuallySkipped) {
|
||||||
private static long lastSegmentSkippedTime;
|
|
||||||
|
|
||||||
private static void skipSegment(@NonNull SponsorSegment segmentToSkip, boolean userManuallySkipped) {
|
|
||||||
try {
|
try {
|
||||||
SponsorBlockViewController.hideSkipHighlightButton();
|
SponsorBlockViewController.hideSkipHighlightButton();
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
@@ -525,7 +637,7 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "Skipping segment: " + segmentToSkip);
|
Logger.printDebug(() -> "Skipping segment: " + segmentToSkip + " videoState: " + VideoState.getCurrent());
|
||||||
lastSegmentSkipped = segmentToSkip;
|
lastSegmentSkipped = segmentToSkip;
|
||||||
lastSegmentSkippedTime = now;
|
lastSegmentSkippedTime = now;
|
||||||
setSegmentCurrentlyPlaying(null);
|
setSegmentCurrentlyPlaying(null);
|
||||||
@@ -535,29 +647,39 @@ public class SegmentPlaybackController {
|
|||||||
highlightSegmentInitialShowEndTime = 0;
|
highlightSegmentInitialShowEndTime = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set or update undo skip range.
|
||||||
|
Range<Long> range = segmentToSkip.getUndoRange();
|
||||||
|
if (undoAutoSkipRange == null) {
|
||||||
|
Logger.printDebug(() -> "Setting new undo range to: " + range);
|
||||||
|
undoAutoSkipRange = range;
|
||||||
|
} else {
|
||||||
|
Range<Long> extendedRange = undoAutoSkipRange.extend(range);
|
||||||
|
Logger.printDebug(() -> "Extending undo range from: " + undoAutoSkipRange +
|
||||||
|
" to: " + extendedRange);
|
||||||
|
undoAutoSkipRange = extendedRange;
|
||||||
|
}
|
||||||
|
undoAutoSkipRangeToast = undoAutoSkipRange;
|
||||||
|
|
||||||
// If the seek is successful, then the seek causes a recursive call back into this class.
|
// If the seek is successful, then the seek causes a recursive call back into this class.
|
||||||
final boolean seekSuccessful = VideoInformation.seekTo(segmentToSkip.end);
|
final boolean seekSuccessful = VideoInformation.seekTo(segmentToSkip.end);
|
||||||
if (!seekSuccessful) {
|
if (!seekSuccessful) {
|
||||||
// can happen when switching videos and is normal
|
// Can happen when switching videos and is normal.
|
||||||
Logger.printDebug(() -> "Could not skip segment (seek unsuccessful): " + segmentToSkip);
|
Logger.printDebug(() -> "Could not skip segment (seek unsuccessful): " + segmentToSkip);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final boolean videoIsPaused = VideoState.getCurrent() == VideoState.PAUSED;
|
|
||||||
if (!userManuallySkipped) {
|
if (!userManuallySkipped) {
|
||||||
// check for any smaller embedded segments, and count those as autoskipped
|
// Check for any smaller embedded segments, and count those as auto-skipped.
|
||||||
final boolean showSkipToast = Settings.SB_TOAST_ON_SKIP.get();
|
final boolean showSkipToast = Settings.SB_TOAST_ON_SKIP.get();
|
||||||
for (final SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
|
for (SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
|
||||||
if (segmentToSkip.end < otherSegment.start) {
|
if (segmentToSkip.end < otherSegment.start) {
|
||||||
break; // no other segments can be contained
|
break; // No other segments can be contained.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (otherSegment == segmentToSkip ||
|
if (otherSegment == segmentToSkip ||
|
||||||
(otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) {
|
(otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) {
|
||||||
otherSegment.didAutoSkipped = true;
|
otherSegment.didAutoSkipped = true;
|
||||||
// Do not show a toast if the user is scrubbing thru a paused video.
|
if (showSkipToast) {
|
||||||
// Cannot do this video state check in setTime or earlier in this method, as the video state may not be up to date.
|
|
||||||
// So instead, only hide toasts because all other skip logic done while paused causes no harm.
|
|
||||||
if (showSkipToast && !videoIsPaused) {
|
|
||||||
showSkippedSegmentToast(otherSegment);
|
showSkippedSegmentToast(otherSegment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,7 +689,7 @@ public class SegmentPlaybackController {
|
|||||||
if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
|
if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
|
||||||
removeUnsubmittedSegments();
|
removeUnsubmittedSegments();
|
||||||
SponsorBlockUtils.setNewSponsorSegmentPreviewed();
|
SponsorBlockUtils.setNewSponsorSegmentPreviewed();
|
||||||
} else if (!videoIsPaused) {
|
} else if (VideoState.getCurrent() != VideoState.PAUSED) {
|
||||||
SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
|
SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@@ -575,31 +697,49 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the segment should be auto-skipped _and_ if undo autoskip is not active.
|
||||||
|
*/
|
||||||
|
private static boolean shouldAutoSkipAndUndoSkipNotActive(SponsorSegment segment, long currentVideoTime) {
|
||||||
|
return segment.shouldAutoSkip() && (undoAutoSkipRange == null
|
||||||
|
|| !undoAutoSkipRange.contains(currentVideoTime));
|
||||||
|
}
|
||||||
|
|
||||||
private static int toastNumberOfSegmentsSkipped;
|
private static void showSkippedSegmentToast(SponsorSegment segment) {
|
||||||
@Nullable
|
|
||||||
private static SponsorSegment toastSegmentSkipped;
|
|
||||||
|
|
||||||
private static void showSkippedSegmentToast(@NonNull SponsorSegment segment) {
|
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
toastNumberOfSegmentsSkipped++;
|
|
||||||
if (toastNumberOfSegmentsSkipped > 1) {
|
|
||||||
return; // toast already scheduled
|
|
||||||
}
|
|
||||||
toastSegmentSkipped = segment;
|
toastSegmentSkipped = segment;
|
||||||
|
if (toastNumberOfSegmentsSkipped++ > 0) {
|
||||||
|
return; // Toast is already scheduled.
|
||||||
|
}
|
||||||
|
|
||||||
final long delayToToastMilliseconds = 250; // also the maximum time between skips to be considered skipping multiple segments
|
// Maximum time between skips to be considered skipping multiple segments.
|
||||||
|
final long delayToToastMilliseconds = 250;
|
||||||
Utils.runOnMainThreadDelayed(() -> {
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
try {
|
try {
|
||||||
if (toastSegmentSkipped == null) { // video was changed just after skipping segment
|
// Do not show a toast if the user is scrubbing thru a paused video.
|
||||||
|
// Cannot do this video state check in setTime or before calling this this method,
|
||||||
|
// as the video state may not be up to date. So instead, only ignore the toast
|
||||||
|
// just before it's about to show since the video state is up to date.
|
||||||
|
if (VideoState.getCurrent() == VideoState.PAUSED) {
|
||||||
|
Logger.printDebug(() -> "Ignoring scheduled toast as video state is paused");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PlayerType.getCurrent() == PlayerType.WATCH_WHILE_PICTURE_IN_PICTURE) {
|
||||||
|
Logger.printDebug(() -> "Not showing autoskip toast as playback is PiP");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toastSegmentSkipped == null || undoAutoSkipRangeToast == null) {
|
||||||
|
// Video was changed immediately after skipping segment.
|
||||||
Logger.printDebug(() -> "Ignoring old scheduled show toast");
|
Logger.printDebug(() -> "Ignoring old scheduled show toast");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Utils.showToastShort(toastNumberOfSegmentsSkipped == 1
|
String message = toastNumberOfSegmentsSkipped == 1
|
||||||
? toastSegmentSkipped.getSkippedToastText()
|
? toastSegmentSkipped.getSkippedToastText()
|
||||||
: str("revanced_sb_skipped_multiple_segments"));
|
: str("revanced_sb_skipped_multiple_segments");
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "showSkippedSegmentToast failure", ex);
|
showAutoSkipToast(message, undoAutoSkipRangeToast);
|
||||||
} finally {
|
} finally {
|
||||||
toastNumberOfSegmentsSkipped = 0;
|
toastNumberOfSegmentsSkipped = 0;
|
||||||
toastSegmentSkipped = null;
|
toastSegmentSkipped = null;
|
||||||
@@ -607,13 +747,126 @@ public class SegmentPlaybackController {
|
|||||||
}, delayToToastMilliseconds);
|
}, delayToToastMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void showAutoSkipToast(String messageToToast, Range<Long> rangeToUndo) {
|
||||||
|
Objects.requireNonNull(messageToToast);
|
||||||
|
Utils.verifyOnMainThread();
|
||||||
|
|
||||||
|
Context currentContext = SponsorBlockViewController.getOverLaysViewGroupContext();
|
||||||
|
if (currentContext == null) {
|
||||||
|
Logger.printException(() -> "Cannot show toast (context is null): " + messageToToast);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
||||||
|
|
||||||
|
Dialog dialog = new Dialog(currentContext);
|
||||||
|
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||||
|
// Do not dismiss dialog if tapped outside the dialog bounds.
|
||||||
|
dialog.setCanceledOnTouchOutside(false);
|
||||||
|
|
||||||
|
LinearLayout mainLayout = new LinearLayout(currentContext);
|
||||||
|
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
final int dip8 = dipToPixels(8);
|
||||||
|
final int dip16 = dipToPixels(16);
|
||||||
|
mainLayout.setPadding(dip16, dip8, dip16, dip8);
|
||||||
|
mainLayout.setGravity(Gravity.CENTER);
|
||||||
|
mainLayout.setMinimumHeight(dipToPixels(48));
|
||||||
|
|
||||||
|
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
|
||||||
|
Utils.createCornerRadii(20), null, null));
|
||||||
|
background.getPaint().setColor(Utils.getDialogBackgroundColor());
|
||||||
|
mainLayout.setBackground(background);
|
||||||
|
|
||||||
|
TextView textView = new TextView(currentContext);
|
||||||
|
textView.setText(messageToToast);
|
||||||
|
textView.setTextSize(14);
|
||||||
|
textView.setTextColor(Utils.getAppForegroundColor());
|
||||||
|
textView.setGravity(Gravity.CENTER);
|
||||||
|
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
|
);
|
||||||
|
textParams.gravity = Gravity.CENTER;
|
||||||
|
textView.setLayoutParams(textParams);
|
||||||
|
mainLayout.addView(textView);
|
||||||
|
mainLayout.setAlpha(0.8f); // Opacity for the entire dialog.
|
||||||
|
|
||||||
|
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
|
||||||
|
Animation fadeIn = Utils.getResourceAnimation("fade_in");
|
||||||
|
Animation fadeOut = Utils.getResourceAnimation("fade_out");
|
||||||
|
fadeIn.setDuration(fadeDurationFast);
|
||||||
|
fadeOut.setDuration(fadeDurationFast);
|
||||||
|
fadeOut.setAnimationListener(new Animation.AnimationListener() {
|
||||||
|
public void onAnimationStart(Animation animation) { }
|
||||||
|
public void onAnimationEnd(Animation animation) {
|
||||||
|
if (dialog.isShowing()) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void onAnimationRepeat(Animation animation) { }
|
||||||
|
});
|
||||||
|
|
||||||
|
mainLayout.setOnClickListener(v -> {
|
||||||
|
try {
|
||||||
|
Logger.printDebug(() -> "Undoing autoskip using range: " + rangeToUndo);
|
||||||
|
// Restore undo autoskip range since it's already cleared by now.
|
||||||
|
undoAutoSkipRange = rangeToUndo;
|
||||||
|
VideoInformation.seekTo(rangeToUndo.getLower());
|
||||||
|
|
||||||
|
mainLayout.startAnimation(fadeOut);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "showToastShortWithTapAction setOnClickListener failure", ex);
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mainLayout.setClickable(true);
|
||||||
|
dialog.setContentView(mainLayout);
|
||||||
|
|
||||||
|
Window window = dialog.getWindow();
|
||||||
|
if (window != null) {
|
||||||
|
// Remove window animations and use custom fade animation.
|
||||||
|
window.setWindowAnimations(0);
|
||||||
|
|
||||||
|
WindowManager.LayoutParams params = window.getAttributes();
|
||||||
|
params.gravity = Gravity.BOTTOM;
|
||||||
|
params.y = dipToPixels(72);
|
||||||
|
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics();
|
||||||
|
int portraitWidth = (int) (displayMetrics.widthPixels * 0.6);
|
||||||
|
|
||||||
|
if (Resources.getSystem().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
portraitWidth = (int) Math.min(portraitWidth, displayMetrics.heightPixels * 0.6);
|
||||||
|
}
|
||||||
|
params.width = portraitWidth;
|
||||||
|
params.dimAmount = 0.0f;
|
||||||
|
window.setAttributes(params);
|
||||||
|
window.setBackgroundDrawable(null);
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dismissUndoToast()) {
|
||||||
|
Logger.printDebug(() -> "Dismissed previous skip toast that was still on screen");
|
||||||
|
}
|
||||||
|
toastDialogRef = new WeakReference<>(dialog);
|
||||||
|
|
||||||
|
mainLayout.startAnimation(fadeIn);
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
// Fade out and dismiss the dialog if the user does not undo the skip.
|
||||||
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
|
if (dialog.isShowing()) {
|
||||||
|
mainLayout.startAnimation(fadeOut);
|
||||||
|
}
|
||||||
|
}, getToastDuration());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param segment can be either a highlight or a regular manual skip segment.
|
* @param segment can be either a highlight or a regular manual skip segment.
|
||||||
*/
|
*/
|
||||||
public static void onSkipSegmentClicked(@NonNull SponsorSegment segment) {
|
public static void onSkipSegmentClicked(SponsorSegment segment) {
|
||||||
try {
|
try {
|
||||||
if (segment != highlightSegment && segment != segmentCurrentlyPlaying) {
|
if (segment != highlightSegment && segment != segmentCurrentlyPlaying) {
|
||||||
Logger.printException(() -> "error: segment not available to skip"); // should never happen
|
Logger.printException(() -> "error: segment not available to skip"); // Should never happen.
|
||||||
SponsorBlockViewController.hideSkipSegmentButton();
|
SponsorBlockViewController.hideSkipSegmentButton();
|
||||||
SponsorBlockViewController.hideSkipHighlightButton();
|
SponsorBlockViewController.hideSkipHighlightButton();
|
||||||
return;
|
return;
|
||||||
@@ -628,7 +881,7 @@ public class SegmentPlaybackController {
|
|||||||
* Injection point
|
* Injection point
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static void setSponsorBarRect(final Object self) {
|
public static void setSponsorBarRect(Object self) {
|
||||||
try {
|
try {
|
||||||
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
@@ -651,7 +904,7 @@ public class SegmentPlaybackController {
|
|||||||
private static void setSponsorBarAbsoluteRight(Rect rect) {
|
private static void setSponsorBarAbsoluteRight(Rect rect) {
|
||||||
final int right = rect.right;
|
final int right = rect.right;
|
||||||
if (sponsorAbsoluteBarRight != right) {
|
if (sponsorAbsoluteBarRight != right) {
|
||||||
Logger.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
|
Logger.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
|
||||||
sponsorAbsoluteBarRight = right;
|
sponsorAbsoluteBarRight = right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -726,12 +979,6 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Actual screen pixel width to use for the highlight segment time bar.
|
|
||||||
*/
|
|
||||||
private static final int highlightSegmentTimeBarScreenWidth
|
|
||||||
= Utils.dipToPixels(HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@@ -752,9 +999,9 @@ public class SegmentPlaybackController {
|
|||||||
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
|
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
|
||||||
final float right;
|
final float right;
|
||||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||||
right = left + highlightSegmentTimeBarScreenWidth;
|
right = left + HIGHLIGHT_SEGMENT_DRAW_BAR_WIDTH;
|
||||||
} else {
|
} else {
|
||||||
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
||||||
}
|
}
|
||||||
canvas.drawRect(left, top, right, bottom, segment.category.paint);
|
canvas.drawRect(left, top, right, bottom, segment.category.paint);
|
||||||
}
|
}
|
||||||
@@ -762,5 +1009,4 @@ public class SegmentPlaybackController {
|
|||||||
Logger.printException(() -> "drawSponsorTimeBars failure", ex);
|
Logger.printException(() -> "drawSponsorTimeBars failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package app.revanced.extension.youtube.sponsorblock;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.util.Pair;
|
||||||
import android.util.Patterns;
|
import android.util.Patterns;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -56,7 +58,7 @@ public class SponsorBlockSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < categorySelectionsArray.length(); i++) {
|
for (int i = 0, length = categorySelectionsArray.length(); i < length; i++) {
|
||||||
JSONObject categorySelectionObject = categorySelectionsArray.getJSONObject(i);
|
JSONObject categorySelectionObject = categorySelectionsArray.getJSONObject(i);
|
||||||
|
|
||||||
String categoryKey = categorySelectionObject.getString("name");
|
String categoryKey = categorySelectionObject.getString("name");
|
||||||
@@ -181,13 +183,25 @@ public class SponsorBlockSettings {
|
|||||||
// If user has a SponsorBlock user id then show a warning.
|
// If user has a SponsorBlock user id then show a warning.
|
||||||
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId()
|
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId()
|
||||||
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) {
|
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) {
|
||||||
new AlertDialog.Builder(dialogContext)
|
// Create the custom dialog.
|
||||||
.setMessage(str("revanced_sb_settings_revanced_export_user_id_warning"))
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
.setNeutralButton(str("revanced_sb_settings_revanced_export_user_id_warning_dismiss"),
|
dialogContext,
|
||||||
(dialog, which) -> Settings.SB_HIDE_EXPORT_WARNING.save(true))
|
null, // No title.
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
str("revanced_sb_settings_revanced_export_user_id_warning"), // Message.
|
||||||
.setCancelable(false)
|
null, // No EditText.
|
||||||
.show();
|
null, // OK button text.
|
||||||
|
() -> {}, // OK button action (dismiss only).
|
||||||
|
null, // No cancel button action.
|
||||||
|
str("revanced_sb_settings_revanced_export_user_id_warning_dismiss"), // Neutral button text.
|
||||||
|
() -> Settings.SB_HIDE_EXPORT_WARNING.save(true), // Neutral button action.
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set dialog as non-cancelable.
|
||||||
|
dialogPair.first.setCancelable(false);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -223,13 +223,18 @@ public class SponsorBlockUtils {
|
|||||||
Logger.printException(() -> "invalid parameters");
|
Logger.printException(() -> "invalid parameters");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearUnsubmittedSegmentTimes();
|
clearUnsubmittedSegmentTimes();
|
||||||
Utils.runOnBackgroundThread(() -> {
|
Utils.runOnBackgroundThread(() -> {
|
||||||
SBRequester.submitSegments(videoId, segmentCategory.keyValue, start, end, videoLength);
|
try {
|
||||||
SegmentPlaybackController.executeDownloadSegments(videoId);
|
SBRequester.submitSegments(videoId, segmentCategory.keyValue, start, end, videoLength);
|
||||||
|
SegmentPlaybackController.executeDownloadSegments(videoId);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "submitNewSegment failure", ex);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Unable to submit segment", e);
|
Logger.printException(() -> "submitNewSegment failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +371,7 @@ public class SponsorBlockUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void sendViewRequestAsync(@NonNull SponsorSegment segment) {
|
static void sendViewRequestAsync(SponsorSegment segment) {
|
||||||
if (segment.recordedAsSkipped || segment.category == SegmentCategory.UNSUBMITTED) {
|
if (segment.recordedAsSkipped || segment.category == SegmentCategory.UNSUBMITTED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -409,7 +414,7 @@ public class SponsorBlockUtils {
|
|||||||
return statsNumberFormatter.format(viewCount);
|
return statsNumberFormatter.format(viewCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long parseSegmentTime(@NonNull String time) {
|
private static long parseSegmentTime(String time) {
|
||||||
Matcher matcher = manualEditTimePattern.matcher(time);
|
Matcher matcher = manualEditTimePattern.matcher(time);
|
||||||
if (!matcher.matches()) {
|
if (!matcher.matches()) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -419,9 +424,12 @@ public class SponsorBlockUtils {
|
|||||||
String secondsStr = matcher.group(4);
|
String secondsStr = matcher.group(4);
|
||||||
String millisecondsStr = matcher.group(6); // Milliseconds is optional.
|
String millisecondsStr = matcher.group(6); // Milliseconds is optional.
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
||||||
|
//noinspection ConstantConditions
|
||||||
final int minutes = Integer.parseInt(minutesStr);
|
final int minutes = Integer.parseInt(minutesStr);
|
||||||
|
//noinspection ConstantConditions
|
||||||
final int seconds = Integer.parseInt(secondsStr);
|
final int seconds = Integer.parseInt(secondsStr);
|
||||||
final int milliseconds;
|
final int milliseconds;
|
||||||
if (millisecondsStr != null) {
|
if (millisecondsStr != null) {
|
||||||
@@ -468,32 +476,29 @@ public class SponsorBlockUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getTimeSavedString(long totalSecondsSaved) {
|
public static String getTimeSavedString(long totalSecondsSaved) {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
||||||
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
final long hours = duration.toHours();
|
||||||
final long hours = duration.toHours();
|
final long minutes = duration.toMinutes() % 60;
|
||||||
final long minutes = duration.toMinutes() % 60;
|
|
||||||
|
|
||||||
// Format all numbers so non-western numbers use a consistent appearance.
|
// Format all numbers so non-western numbers use a consistent appearance.
|
||||||
String minutesFormatted = statsNumberFormatter.format(minutes);
|
String minutesFormatted = statsNumberFormatter.format(minutes);
|
||||||
if (hours > 0) {
|
if (hours > 0) {
|
||||||
String hoursFormatted = statsNumberFormatter.format(hours);
|
String hoursFormatted = statsNumberFormatter.format(hours);
|
||||||
return str("revanced_sb_stats_saved_hour_format", hoursFormatted, minutesFormatted);
|
return str("revanced_sb_stats_saved_hour_format", hoursFormatted, minutesFormatted);
|
||||||
}
|
|
||||||
|
|
||||||
final long seconds = duration.getSeconds() % 60;
|
|
||||||
String secondsFormatted = statsNumberFormatter.format(seconds);
|
|
||||||
if (minutes > 0) {
|
|
||||||
return str("revanced_sb_stats_saved_minute_format", minutesFormatted, secondsFormatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
return str("revanced_sb_stats_saved_second_format", secondsFormatted);
|
|
||||||
}
|
}
|
||||||
return "error"; // will never be reached. YouTube requires Android O or greater
|
|
||||||
|
final long seconds = duration.getSeconds() % 60;
|
||||||
|
String secondsFormatted = statsNumberFormatter.format(seconds);
|
||||||
|
if (minutes > 0) {
|
||||||
|
return str("revanced_sb_stats_saved_minute_format", minutesFormatted, secondsFormatted);
|
||||||
|
}
|
||||||
|
|
||||||
|
return str("revanced_sb_stats_saved_second_format", secondsFormatted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class EditByHandSaveDialogListener implements DialogInterface.OnClickListener {
|
private static class EditByHandSaveDialogListener implements DialogInterface.OnClickListener {
|
||||||
boolean settingStart;
|
private boolean settingStart;
|
||||||
WeakReference<EditText> editTextRef = new WeakReference<>(null);
|
private WeakReference<EditText> editTextRef = new WeakReference<>(null);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
@@ -512,10 +517,11 @@ public class SponsorBlockUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settingStart)
|
if (settingStart) {
|
||||||
newSponsorSegmentStartMillis = Math.max(time, 0);
|
newSponsorSegmentStartMillis = Math.max(time, 0);
|
||||||
else
|
} else {
|
||||||
newSponsorSegmentEndMillis = time;
|
newSponsorSegmentEndMillis = time;
|
||||||
|
}
|
||||||
|
|
||||||
if (which == DialogInterface.BUTTON_NEUTRAL)
|
if (which == DialogInterface.BUTTON_NEUTRAL)
|
||||||
editByHandDialogListener.onClick(dialog, settingStart ?
|
editByHandDialogListener.onClick(dialog, settingStart ?
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package app.revanced.extension.youtube.sponsorblock.objects;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||||
|
import static app.revanced.extension.shared.Utils.dipToPixels;
|
||||||
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
|
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
|
||||||
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
|
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -15,13 +15,10 @@ import android.preference.ListPreference;
|
|||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.util.Pair;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.*;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.GridLayout;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.ColorInt;
|
import androidx.annotation.ColorInt;
|
||||||
|
|
||||||
@@ -52,6 +49,7 @@ public class SegmentCategoryListPreference extends ListPreference {
|
|||||||
private EditText dialogColorEditText;
|
private EditText dialogColorEditText;
|
||||||
private EditText dialogOpacityEditText;
|
private EditText dialogOpacityEditText;
|
||||||
private ColorPickerView dialogColorPickerView;
|
private ColorPickerView dialogColorPickerView;
|
||||||
|
private Dialog dialog;
|
||||||
|
|
||||||
public SegmentCategoryListPreference(Context context, SegmentCategory category) {
|
public SegmentCategoryListPreference(Context context, SegmentCategory category) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -75,30 +73,45 @@ public class SegmentCategoryListPreference extends ListPreference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
protected void showDialog(Bundle state) {
|
||||||
try {
|
try {
|
||||||
Utils.setEditTextDialogTheme(builder);
|
Context context = getContext();
|
||||||
|
|
||||||
categoryColor = category.getColorNoOpacity();
|
categoryColor = category.getColorNoOpacity();
|
||||||
categoryOpacity = category.getOpacity();
|
categoryOpacity = category.getOpacity();
|
||||||
|
selectedDialogEntryIndex = findIndexOfValue(getValue());
|
||||||
|
|
||||||
Context context = builder.getContext();
|
// Create the main layout for the dialog content.
|
||||||
LinearLayout mainLayout = new LinearLayout(context);
|
LinearLayout contentLayout = new LinearLayout(context);
|
||||||
mainLayout.setOrientation(LinearLayout.VERTICAL);
|
contentLayout.setOrientation(LinearLayout.VERTICAL);
|
||||||
mainLayout.setPadding(70, 0, 70, 0);
|
|
||||||
|
// Add behavior selection radio buttons.
|
||||||
|
RadioGroup radioGroup = new RadioGroup(context);
|
||||||
|
radioGroup.setOrientation(RadioGroup.VERTICAL);
|
||||||
|
CharSequence[] entries = getEntries();
|
||||||
|
for (int i = 0; i < entries.length; i++) {
|
||||||
|
RadioButton radioButton = new RadioButton(context);
|
||||||
|
radioButton.setText(entries[i]);
|
||||||
|
radioButton.setId(i);
|
||||||
|
radioButton.setChecked(i == selectedDialogEntryIndex);
|
||||||
|
radioGroup.addView(radioButton);
|
||||||
|
}
|
||||||
|
radioGroup.setOnCheckedChangeListener((group, checkedId) -> selectedDialogEntryIndex = checkedId);
|
||||||
|
radioGroup.setPadding(dipToPixels(10), 0, 0, 0);
|
||||||
|
contentLayout.addView(radioGroup);
|
||||||
|
|
||||||
// Inflate the color picker view.
|
// Inflate the color picker view.
|
||||||
View colorPickerContainer = LayoutInflater.from(context)
|
View colorPickerContainer = LayoutInflater.from(context)
|
||||||
.inflate(getResourceIdentifier("revanced_color_picker", "layout"), null);
|
.inflate(getResourceIdentifier("revanced_color_picker", "layout"), null);
|
||||||
dialogColorPickerView = colorPickerContainer.findViewById(
|
dialogColorPickerView = colorPickerContainer.findViewById(
|
||||||
getResourceIdentifier("color_picker_view", "id"));
|
getResourceIdentifier("revanced_color_picker_view", "id"));
|
||||||
dialogColorPickerView.setColor(categoryColor);
|
dialogColorPickerView.setColor(categoryColor);
|
||||||
mainLayout.addView(colorPickerContainer);
|
contentLayout.addView(colorPickerContainer);
|
||||||
|
|
||||||
// Grid layout for color and opacity inputs.
|
// Grid layout for color and opacity inputs.
|
||||||
GridLayout gridLayout = new GridLayout(context);
|
GridLayout gridLayout = new GridLayout(context);
|
||||||
gridLayout.setColumnCount(3);
|
gridLayout.setColumnCount(3);
|
||||||
gridLayout.setRowCount(2);
|
gridLayout.setRowCount(2);
|
||||||
|
gridLayout.setPadding(dipToPixels(16), 0, 0, 0);
|
||||||
|
|
||||||
GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams();
|
GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams();
|
||||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||||
@@ -111,7 +124,7 @@ public class SegmentCategoryListPreference extends ListPreference {
|
|||||||
gridParams = new GridLayout.LayoutParams();
|
gridParams = new GridLayout.LayoutParams();
|
||||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||||
gridParams.columnSpec = GridLayout.spec(1); // Second column.
|
gridParams.columnSpec = GridLayout.spec(1); // Second column.
|
||||||
gridParams.setMargins(0, 0, 10, 0);
|
gridParams.setMargins(0, 0, dipToPixels(10), 0);
|
||||||
dialogColorDotView = new TextView(context);
|
dialogColorDotView = new TextView(context);
|
||||||
dialogColorDotView.setLayoutParams(gridParams);
|
dialogColorDotView.setLayoutParams(gridParams);
|
||||||
gridLayout.addView(dialogColorDotView);
|
gridLayout.addView(dialogColorDotView);
|
||||||
@@ -162,8 +175,7 @@ public class SegmentCategoryListPreference extends ListPreference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dialogColorEditText.setLayoutParams(gridParams);
|
gridLayout.addView(dialogColorEditText, gridParams);
|
||||||
gridLayout.addView(dialogColorEditText);
|
|
||||||
|
|
||||||
gridParams = new GridLayout.LayoutParams();
|
gridParams = new GridLayout.LayoutParams();
|
||||||
gridParams.rowSpec = GridLayout.spec(1); // Second row.
|
gridParams.rowSpec = GridLayout.spec(1); // Second row.
|
||||||
@@ -226,11 +238,70 @@ public class SegmentCategoryListPreference extends ListPreference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dialogOpacityEditText.setLayoutParams(gridParams);
|
gridLayout.addView(dialogOpacityEditText, gridParams);
|
||||||
gridLayout.addView(dialogOpacityEditText);
|
|
||||||
updateOpacityText();
|
updateOpacityText();
|
||||||
|
|
||||||
mainLayout.addView(gridLayout);
|
contentLayout.addView(gridLayout);
|
||||||
|
|
||||||
|
// Create ScrollView to wrap the content layout.
|
||||||
|
ScrollView contentScrollView = new ScrollView(context);
|
||||||
|
contentScrollView.setVerticalScrollBarEnabled(false); // Disable vertical scrollbar.
|
||||||
|
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); // Disable overscroll effect.
|
||||||
|
LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
0,
|
||||||
|
1.0f
|
||||||
|
);
|
||||||
|
contentScrollView.setLayoutParams(scrollViewParams);
|
||||||
|
contentScrollView.addView(contentLayout);
|
||||||
|
|
||||||
|
// Create the custom dialog.
|
||||||
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
category.title.toString(), // Title.
|
||||||
|
null, // No message (replaced by contentLayout).
|
||||||
|
null, // No EditText.
|
||||||
|
null, // OK button text.
|
||||||
|
() -> {
|
||||||
|
// OK button action.
|
||||||
|
if (selectedDialogEntryIndex >= 0 && getEntryValues() != null) {
|
||||||
|
String value = getEntryValues()[selectedDialogEntryIndex].toString();
|
||||||
|
if (callChangeListener(value)) {
|
||||||
|
setValue(value);
|
||||||
|
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
|
||||||
|
SegmentCategory.updateEnabledCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
category.setColor(dialogColorEditText.getText().toString());
|
||||||
|
category.setOpacity(categoryOpacity);
|
||||||
|
} catch (IllegalArgumentException ex) {
|
||||||
|
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
str("revanced_settings_reset_color"), // Neutral button text.
|
||||||
|
() -> {
|
||||||
|
// Neutral button action (Reset).
|
||||||
|
try {
|
||||||
|
// Setting view color causes callback to update the UI.
|
||||||
|
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
|
||||||
|
|
||||||
|
categoryOpacity = category.getOpacityDefault();
|
||||||
|
updateOpacityText();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "resetButton onClick failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false // Do not dismiss dialog on Neutral button click.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add the ScrollView to the dialog's main layout.
|
||||||
|
LinearLayout dialogMainLayout = dialogPair.second;
|
||||||
|
dialogMainLayout.addView(contentScrollView, dialogMainLayout.getChildCount() - 1);
|
||||||
|
|
||||||
// Set up color picker listener.
|
// Set up color picker listener.
|
||||||
// Do last to prevent listener callbacks while setting up view.
|
// Do last to prevent listener callbacks while setting up view.
|
||||||
@@ -247,69 +318,25 @@ public class SegmentCategoryListPreference extends ListPreference {
|
|||||||
dialogColorEditText.setSelection(hexColor.length());
|
dialogColorEditText.setSelection(hexColor.length());
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.setView(mainLayout);
|
// Show the dialog.
|
||||||
builder.setTitle(category.title.toString());
|
dialog = dialogPair.first;
|
||||||
|
dialog.show();
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
|
||||||
onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
|
||||||
});
|
|
||||||
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
|
|
||||||
selectedDialogEntryIndex = findIndexOfValue(getValue());
|
|
||||||
builder.setSingleChoiceItems(getEntries(), selectedDialogEntryIndex,
|
|
||||||
(dialog, which) -> selectedDialogEntryIndex = which);
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "onPrepareDialogBuilder failure", ex);
|
Logger.printException(() -> "showDialog failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void showDialog(Bundle state) {
|
|
||||||
super.showDialog(state);
|
|
||||||
|
|
||||||
// Do not close dialog when reset is pressed.
|
|
||||||
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
|
|
||||||
button.setOnClickListener(view -> {
|
|
||||||
try {
|
|
||||||
// Setting view color causes callback to update the UI.
|
|
||||||
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
|
|
||||||
|
|
||||||
categoryOpacity = category.getOpacityDefault();
|
|
||||||
updateOpacityText();
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "setOnClickListener failure", ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDialogClosed(boolean positiveResult) {
|
protected void onDialogClosed(boolean positiveResult) {
|
||||||
try {
|
// Nullify dialog references.
|
||||||
if (positiveResult && selectedDialogEntryIndex >= 0 && getEntryValues() != null) {
|
dialogColorDotView = null;
|
||||||
String value = getEntryValues()[selectedDialogEntryIndex].toString();
|
dialogColorEditText = null;
|
||||||
if (callChangeListener(value)) {
|
dialogOpacityEditText = null;
|
||||||
setValue(value);
|
dialogColorPickerView = null;
|
||||||
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
|
|
||||||
SegmentCategory.updateEnabledCategories();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
if (dialog != null) {
|
||||||
category.setColor(dialogColorEditText.getText().toString());
|
dialog.dismiss();
|
||||||
category.setOpacity(categoryOpacity);
|
dialog = null;
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
Utils.showToastShort(str("revanced_settings_color_invalid"));
|
|
||||||
}
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Logger.printException(() -> "onDialogClosed failure", ex);
|
|
||||||
} finally {
|
|
||||||
dialogColorDotView = null;
|
|
||||||
dialogColorEditText = null;
|
|
||||||
dialogOpacityEditText = null;
|
|
||||||
dialogColorPickerView = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.sf;
|
import static app.revanced.extension.shared.StringRef.sf;
|
||||||
|
|
||||||
|
import android.util.Range;
|
||||||
|
|
||||||
public class SponsorSegment implements Comparable<SponsorSegment> {
|
public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||||
|
|
||||||
public enum SegmentVote {
|
public enum SegmentVote {
|
||||||
UPVOTE(sf("revanced_sb_vote_upvote"), 1,false),
|
UPVOTE(sf("revanced_sb_vote_upvote"), 1,false),
|
||||||
DOWNVOTE(sf("revanced_sb_vote_downvote"), 0, true),
|
DOWNVOTE(sf("revanced_sb_vote_downvote"), 0, true),
|
||||||
@@ -38,7 +41,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
|||||||
@NonNull
|
@NonNull
|
||||||
public final SegmentCategory category;
|
public final SegmentCategory category;
|
||||||
/**
|
/**
|
||||||
* NULL if segment is unsubmitted
|
* NULL if segment is unsubmitted.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public final String UUID;
|
public final String UUID;
|
||||||
@@ -64,33 +67,54 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number
|
* @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number.
|
||||||
*/
|
*/
|
||||||
public boolean startIsNear(long videoTime, long nearThreshold) {
|
public boolean startIsNear(long videoTime, long nearThreshold) {
|
||||||
return Math.abs(start - videoTime) <= nearThreshold;
|
return Math.abs(start - videoTime) <= nearThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number
|
* @param nearThreshold threshold to declare the time parameter is near this segment. Must be a positive number.
|
||||||
*/
|
*/
|
||||||
public boolean endIsNear(long videoTime, long nearThreshold) {
|
public boolean endIsNear(long videoTime, long nearThreshold) {
|
||||||
return Math.abs(end - videoTime) <= nearThreshold;
|
return Math.abs(end - videoTime) <= nearThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return if the time parameter is within this segment
|
* @return if the time parameter is within this segment.
|
||||||
*/
|
*/
|
||||||
public boolean containsTime(long videoTime) {
|
public boolean containsTime(long videoTime) {
|
||||||
return start <= videoTime && videoTime < end;
|
return start <= videoTime && videoTime < end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return if the segment is completely contained inside this segment
|
* @return if the segment is completely contained inside this segment.
|
||||||
*/
|
*/
|
||||||
public boolean containsSegment(SponsorSegment other) {
|
public boolean containsSegment(SponsorSegment other) {
|
||||||
return start <= other.start && other.end <= end;
|
return start <= other.start && other.end <= end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the range has any overlap with this segment.
|
||||||
|
*/
|
||||||
|
public boolean intersectsRange(Range<Long> range) {
|
||||||
|
return range.getLower() < end && range.getUpper() >= start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The start/end time in range form.
|
||||||
|
* Range times are adjusted since it uses inclusive and Segments use exclusive.
|
||||||
|
*
|
||||||
|
* {@link SegmentCategory#HIGHLIGHT} is unique and
|
||||||
|
* returns a range from the start of the video until the highlight.
|
||||||
|
*/
|
||||||
|
public Range<Long> getUndoRange() {
|
||||||
|
final long undoStart = category == SegmentCategory.HIGHLIGHT
|
||||||
|
? 0
|
||||||
|
: start;
|
||||||
|
return Range.create(undoStart, end - 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the length of this segment, in milliseconds. Always a positive number.
|
* @return the length of this segment, in milliseconds. Always a positive number.
|
||||||
*/
|
*/
|
||||||
@@ -99,7 +123,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 'skip segment' UI overlay button text
|
* @return 'skip segment' UI overlay button text.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public String getSkipButtonText() {
|
public String getSkipButtonText() {
|
||||||
@@ -107,7 +131,7 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return 'skipped segment' toast message
|
* @return 'skipped segment' toast message.
|
||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public String getSkippedToastText() {
|
public String getSkippedToastText() {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class SBRequester {
|
|||||||
private SBRequester() {
|
private SBRequester() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) {
|
private static void handleConnectionError(String toastMessage, @Nullable Exception ex) {
|
||||||
if (Settings.SB_TOAST_ON_CONNECTION_ERROR.get()) {
|
if (Settings.SB_TOAST_ON_CONNECTION_ERROR.get()) {
|
||||||
Utils.showToastShort(toastMessage);
|
Utils.showToastShort(toastMessage);
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ public class SBRequester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static SponsorSegment[] getSegments(@NonNull String videoId) {
|
public static SponsorSegment[] getSegments(String videoId) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
List<SponsorSegment> segments = new ArrayList<>();
|
List<SponsorSegment> segments = new ArrayList<>();
|
||||||
try {
|
try {
|
||||||
@@ -113,10 +113,10 @@ public class SBRequester {
|
|||||||
Logger.printException(() -> "getSegments failure", ex);
|
Logger.printException(() -> "getSegments failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crude debug tests to verify random features
|
// Crude debug tests to verify random features.
|
||||||
// Could benefit from:
|
// Could benefit from:
|
||||||
// 1) collection of YouTube videos with test segment times (verify client skip timing matches the video, verify seekbar draws correctly)
|
// 1) Collection of YouTube videos with test segment times (verify client skip timing matches the video, verify seekbar draws correctly).
|
||||||
// 2) unit tests (verify everything else)
|
// 2) Unit tests (verify everything else).
|
||||||
//noinspection ConstantValue
|
//noinspection ConstantValue
|
||||||
if (false) {
|
if (false) {
|
||||||
segments.clear();
|
segments.clear();
|
||||||
@@ -140,10 +140,30 @@ public class SBRequester {
|
|||||||
segments.add(new SponsorSegment(SegmentCategory.SELF_PROMO, "debug", 200000, 330000, false));
|
segments.add(new SponsorSegment(SegmentCategory.SELF_PROMO, "debug", 200000, 330000, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test undo skip functionality.
|
||||||
|
// To test enable 'Autoskip always' for intro and self promo.
|
||||||
|
//noinspection ConstantValue
|
||||||
|
if (false) {
|
||||||
|
// Should autoskip to 12 seconds.
|
||||||
|
// Undoing skip should seek to 2 seconds.
|
||||||
|
// Skip button should show at 2 seconds, and again at 8 seconds.
|
||||||
|
// Self promo at 8 second time should not autoskip.
|
||||||
|
segments.clear();
|
||||||
|
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 2000, 12000, false));
|
||||||
|
segments.add(new SponsorSegment(SegmentCategory.SELF_PROMO, "debug", 8000, 15000, false));
|
||||||
|
|
||||||
|
// Test multiple autoskip dialogs rapidly showing.
|
||||||
|
// Only one toast should be shown at anytime.
|
||||||
|
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 16000, 17000, false));
|
||||||
|
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 18000, 19000, false));
|
||||||
|
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 20000, 21000, false));
|
||||||
|
segments.add(new SponsorSegment(SegmentCategory.INTRO, "debug", 22000, 23000, false));
|
||||||
|
}
|
||||||
|
|
||||||
return segments.toArray(new SponsorSegment[0]);
|
return segments.toArray(new SponsorSegment[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void submitSegments(@NonNull String videoId, @NonNull String category,
|
public static void submitSegments(String videoId, String category,
|
||||||
long startTime, long endTime, long videoLength) {
|
long startTime, long endTime, long videoLength) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
|
|
||||||
@@ -189,7 +209,7 @@ public class SBRequester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void sendSegmentSkippedViewedRequest(@NonNull SponsorSegment segment) {
|
public static void sendSegmentSkippedViewedRequest(SponsorSegment segment) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.VIEWED_SEGMENT, segment.UUID);
|
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.VIEWED_SEGMENT, segment.UUID);
|
||||||
@@ -208,13 +228,13 @@ public class SBRequester {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void voteForSegmentOnBackgroundThread(@NonNull SponsorSegment segment, @NonNull SegmentVote voteOption) {
|
public static void voteForSegmentOnBackgroundThread(SponsorSegment segment, SegmentVote voteOption) {
|
||||||
voteOrRequestCategoryChange(segment, voteOption, null);
|
voteOrRequestCategoryChange(segment, voteOption, null);
|
||||||
}
|
}
|
||||||
public static void voteToChangeCategoryOnBackgroundThread(@NonNull SponsorSegment segment, @NonNull SegmentCategory categoryToVoteFor) {
|
public static void voteToChangeCategoryOnBackgroundThread(SponsorSegment segment, SegmentCategory categoryToVoteFor) {
|
||||||
voteOrRequestCategoryChange(segment, SegmentVote.CATEGORY_CHANGE, categoryToVoteFor);
|
voteOrRequestCategoryChange(segment, SegmentVote.CATEGORY_CHANGE, categoryToVoteFor);
|
||||||
}
|
}
|
||||||
private static void voteOrRequestCategoryChange(@NonNull SponsorSegment segment, @NonNull SegmentVote voteOption, SegmentCategory categoryToVoteFor) {
|
private static void voteOrRequestCategoryChange(SponsorSegment segment, SegmentVote voteOption, SegmentCategory categoryToVoteFor) {
|
||||||
Utils.runOnBackgroundThread(() -> {
|
Utils.runOnBackgroundThread(() -> {
|
||||||
try {
|
try {
|
||||||
String segmentUuid = segment.UUID;
|
String segmentUuid = segment.UUID;
|
||||||
@@ -280,7 +300,7 @@ public class SBRequester {
|
|||||||
* @return NULL if the call was successful. If unsuccessful, an error message is returned.
|
* @return NULL if the call was successful. If unsuccessful, an error message is returned.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static String setUsername(@NonNull String username) {
|
public static String setUsername(String username) {
|
||||||
Utils.verifyOffMainThread();
|
Utils.verifyOffMainThread();
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SponsorBlockSettings.getSBPrivateUserID(), username);
|
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SponsorBlockSettings.getSBPrivateUserID(), username);
|
||||||
@@ -320,14 +340,14 @@ public class SBRequester {
|
|||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
|
|
||||||
private static HttpURLConnection getConnectionFromRoute(@NonNull Route route, String... params) throws IOException {
|
private static HttpURLConnection getConnectionFromRoute(Route route, String... params) throws IOException {
|
||||||
HttpURLConnection connection = Requester.getConnectionFromRoute(Settings.SB_API_URL.get(), route, params);
|
HttpURLConnection connection = Requester.getConnectionFromRoute(Settings.SB_API_URL.get(), route, params);
|
||||||
connection.setConnectTimeout(TIMEOUT_TCP_DEFAULT_MILLISECONDS);
|
connection.setConnectTimeout(TIMEOUT_TCP_DEFAULT_MILLISECONDS);
|
||||||
connection.setReadTimeout(TIMEOUT_HTTP_DEFAULT_MILLISECONDS);
|
connection.setReadTimeout(TIMEOUT_HTTP_DEFAULT_MILLISECONDS);
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JSONObject getJSONObject(@NonNull Route route, String... params) throws IOException, JSONException {
|
private static JSONObject getJSONObject(Route route, String... params) throws IOException, JSONException {
|
||||||
return Requester.parseJSONObject(getConnectionFromRoute(route, params));
|
return Requester.parseJSONObject(getConnectionFromRoute(route, params));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,35 @@
|
|||||||
package app.revanced.extension.youtube.sponsorblock.ui;
|
package app.revanced.extension.youtube.sponsorblock.ui;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
import static app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController.SponsorBlockDuration;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.preference.*;
|
import android.os.Bundle;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceCategory;
|
||||||
|
import android.preference.PreferenceGroup;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Pair;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
|
||||||
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
|
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
||||||
@@ -56,6 +64,8 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
private SwitchPreference trackSkips;
|
private SwitchPreference trackSkips;
|
||||||
private SwitchPreference showTimeWithoutSegments;
|
private SwitchPreference showTimeWithoutSegments;
|
||||||
private SwitchPreference toastOnConnectionError;
|
private SwitchPreference toastOnConnectionError;
|
||||||
|
private CustomDialogListPreference autoHideSkipSegmentButtonDuration;
|
||||||
|
private CustomDialogListPreference showSkipToastDuration;
|
||||||
|
|
||||||
private ResettableEditTextPreference newSegmentStep;
|
private ResettableEditTextPreference newSegmentStep;
|
||||||
private ResettableEditTextPreference minSegmentDuration;
|
private ResettableEditTextPreference minSegmentDuration;
|
||||||
@@ -63,8 +73,8 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
private EditTextPreference importExport;
|
private EditTextPreference importExport;
|
||||||
private Preference apiUrl;
|
private Preference apiUrl;
|
||||||
|
|
||||||
private final List<SegmentCategoryListPreference> segmentCategories = new ArrayList<>();
|
|
||||||
private PreferenceCategory segmentCategory;
|
private PreferenceCategory segmentCategory;
|
||||||
|
private final List<SegmentCategoryListPreference> segmentCategories = new ArrayList<>();
|
||||||
|
|
||||||
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public SponsorBlockPreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
@@ -108,17 +118,23 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
|
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
|
||||||
votingEnabled.setEnabled(enabled);
|
votingEnabled.setEnabled(enabled);
|
||||||
|
|
||||||
autoHideSkipSegmentButton.setEnabled(enabled);
|
|
||||||
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
|
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
|
||||||
|
autoHideSkipSegmentButton.setEnabled(enabled);
|
||||||
|
|
||||||
|
autoHideSkipSegmentButtonDuration.setValue(Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.get().toString());
|
||||||
|
autoHideSkipSegmentButtonDuration.setEnabled(Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.isAvailable());
|
||||||
|
|
||||||
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
|
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
|
||||||
compactSkipButton.setEnabled(enabled);
|
compactSkipButton.setEnabled(enabled);
|
||||||
|
|
||||||
|
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
||||||
|
showSkipToast.setEnabled(enabled);
|
||||||
|
|
||||||
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
|
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
|
||||||
squareLayout.setEnabled(enabled);
|
squareLayout.setEnabled(enabled);
|
||||||
|
|
||||||
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
showSkipToastDuration.setValue(Settings.SB_TOAST_ON_SKIP_DURATION.get().toString());
|
||||||
showSkipToast.setEnabled(enabled);
|
showSkipToastDuration.setEnabled(Settings.SB_TOAST_ON_SKIP_DURATION.isAvailable());
|
||||||
|
|
||||||
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
|
toastOnConnectionError.setChecked(Settings.SB_TOAST_ON_CONNECTION_ERROR.get());
|
||||||
toastOnConnectionError.setEnabled(enabled);
|
toastOnConnectionError.setEnabled(enabled);
|
||||||
@@ -160,7 +176,7 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
try {
|
try {
|
||||||
super.onAttachedToActivity();
|
super.onAttachedToActivity();
|
||||||
|
|
||||||
if (preferencesInitialized) {
|
if (preferencesInitialized) {
|
||||||
if (settingsImported) {
|
if (settingsImported) {
|
||||||
settingsImported = false;
|
settingsImported = false;
|
||||||
updateUI();
|
updateUI();
|
||||||
@@ -199,17 +215,6 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
});
|
});
|
||||||
appearanceCategory.addPreference(votingEnabled);
|
appearanceCategory.addPreference(votingEnabled);
|
||||||
|
|
||||||
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"));
|
|
||||||
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
|
||||||
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
|
|
||||||
updateUI();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
appearanceCategory.addPreference(autoHideSkipSegmentButton);
|
|
||||||
|
|
||||||
compactSkipButton = new SwitchPreference(context);
|
compactSkipButton = new SwitchPreference(context);
|
||||||
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
|
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
|
||||||
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
|
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
|
||||||
@@ -221,25 +226,38 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
});
|
});
|
||||||
appearanceCategory.addPreference(compactSkipButton);
|
appearanceCategory.addPreference(compactSkipButton);
|
||||||
|
|
||||||
squareLayout = new SwitchPreference(context);
|
autoHideSkipSegmentButton = new SwitchPreference(context);
|
||||||
squareLayout.setTitle(str("revanced_sb_square_layout"));
|
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
|
||||||
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
|
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
|
||||||
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
|
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
|
||||||
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
|
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
|
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
appearanceCategory.addPreference(squareLayout);
|
appearanceCategory.addPreference(autoHideSkipSegmentButton);
|
||||||
|
|
||||||
|
String[] durationEntries = Utils.getResourceStringArray("revanced_sb_duration_entries");
|
||||||
|
String[] durationEntryValues = Utils.getResourceStringArray("revanced_sb_duration_entry_values");
|
||||||
|
|
||||||
|
autoHideSkipSegmentButtonDuration = new CustomDialogListPreference(context);
|
||||||
|
autoHideSkipSegmentButtonDuration.setTitle(str("revanced_sb_auto_hide_skip_button_duration"));
|
||||||
|
autoHideSkipSegmentButtonDuration.setSummary(str("revanced_sb_auto_hide_skip_button_duration_sum"));
|
||||||
|
autoHideSkipSegmentButtonDuration.setEntries(durationEntries);
|
||||||
|
autoHideSkipSegmentButtonDuration.setEntryValues(durationEntryValues);
|
||||||
|
autoHideSkipSegmentButtonDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
|
Settings.SB_AUTO_HIDE_SKIP_BUTTON_DURATION.save(
|
||||||
|
SponsorBlockDuration.valueOf((String) newValue)
|
||||||
|
);
|
||||||
|
updateUI();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
appearanceCategory.addPreference(autoHideSkipSegmentButtonDuration);
|
||||||
|
|
||||||
showSkipToast = new SwitchPreference(context);
|
showSkipToast = new SwitchPreference(context);
|
||||||
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
|
showSkipToast.setTitle(str("revanced_sb_general_skiptoast"));
|
||||||
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
|
showSkipToast.setSummaryOn(str("revanced_sb_general_skiptoast_sum_on"));
|
||||||
showSkipToast.setSummaryOff(str("revanced_sb_general_skiptoast_sum_off"));
|
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) -> {
|
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
|
Settings.SB_TOAST_ON_SKIP.save((Boolean) newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
@@ -247,6 +265,20 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
});
|
});
|
||||||
appearanceCategory.addPreference(showSkipToast);
|
appearanceCategory.addPreference(showSkipToast);
|
||||||
|
|
||||||
|
showSkipToastDuration = new CustomDialogListPreference(context);
|
||||||
|
showSkipToastDuration.setTitle(str("revanced_sb_toast_on_skip_duration"));
|
||||||
|
showSkipToastDuration.setSummary(str("revanced_sb_toast_on_skip_duration_sum"));
|
||||||
|
showSkipToastDuration.setEntries(durationEntries);
|
||||||
|
showSkipToastDuration.setEntryValues(durationEntryValues);
|
||||||
|
showSkipToastDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
|
Settings.SB_TOAST_ON_SKIP_DURATION.save(
|
||||||
|
SponsorBlockDuration.valueOf((String) newValue)
|
||||||
|
);
|
||||||
|
updateUI();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
appearanceCategory.addPreference(showSkipToastDuration);
|
||||||
|
|
||||||
showTimeWithoutSegments = new SwitchPreference(context);
|
showTimeWithoutSegments = new SwitchPreference(context);
|
||||||
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
|
showTimeWithoutSegments.setTitle(str("revanced_sb_general_time_without"));
|
||||||
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
|
showTimeWithoutSegments.setSummaryOn(str("revanced_sb_general_time_without_sum_on"));
|
||||||
@@ -258,6 +290,17 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
});
|
});
|
||||||
appearanceCategory.addPreference(showTimeWithoutSegments);
|
appearanceCategory.addPreference(showTimeWithoutSegments);
|
||||||
|
|
||||||
|
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"));
|
||||||
|
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
|
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
|
||||||
|
updateUI();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
appearanceCategory.addPreference(squareLayout);
|
||||||
|
|
||||||
segmentCategory = new PreferenceCategory(context);
|
segmentCategory = new PreferenceCategory(context);
|
||||||
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
|
segmentCategory.setTitle(str("revanced_sb_diff_segments"));
|
||||||
addPreference(segmentCategory);
|
addPreference(segmentCategory);
|
||||||
@@ -279,14 +322,26 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
|
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
|
||||||
Boolean newValue = (Boolean) o;
|
Boolean newValue = (Boolean) o;
|
||||||
if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) {
|
if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) {
|
||||||
new AlertDialog.Builder(preference1.getContext())
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
.setTitle(str("revanced_sb_guidelines_popup_title"))
|
preference1.getContext(),
|
||||||
.setMessage(str("revanced_sb_guidelines_popup_content"))
|
str("revanced_sb_guidelines_popup_title"), // Title.
|
||||||
.setNegativeButton(str("revanced_sb_guidelines_popup_already_read"), null)
|
str("revanced_sb_guidelines_popup_content"), // Message.
|
||||||
.setPositiveButton(str("revanced_sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines())
|
null, // No EditText.
|
||||||
.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true))
|
str("revanced_sb_guidelines_popup_open"), // OK button text.
|
||||||
.setCancelable(false)
|
() -> openGuidelines(), // OK button action.
|
||||||
.show();
|
null, // Cancel button action.
|
||||||
|
str("revanced_sb_guidelines_popup_already_read"), // Neutral button text.
|
||||||
|
() -> {}, // Neutral button action (dismiss only).
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set dialog as non-cancelable.
|
||||||
|
dialogPair.first.setCancelable(false);
|
||||||
|
|
||||||
|
dialogPair.first.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true));
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
}
|
}
|
||||||
Settings.SB_CREATE_NEW_SEGMENT.save(newValue);
|
Settings.SB_CREATE_NEW_SEGMENT.save(newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
@@ -372,16 +427,52 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
generalCategory.addPreference(minSegmentDuration);
|
generalCategory.addPreference(minSegmentDuration);
|
||||||
|
|
||||||
privateUserId = new EditTextPreference(context) {
|
privateUserId = new EditTextPreference(context) {
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
@Override
|
||||||
Utils.setEditTextDialogTheme(builder);
|
protected void showDialog(Bundle state) {
|
||||||
|
try {
|
||||||
|
Context context = getContext();
|
||||||
|
EditText editText = getEditText();
|
||||||
|
|
||||||
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
|
// Set initial EditText value to the current persisted value or empty string.
|
||||||
try {
|
String initialValue = getText() != null ? getText() : "";
|
||||||
Utils.setClipboard(getEditText().getText());
|
editText.setText(initialValue);
|
||||||
} catch (Exception ex) {
|
editText.setSelection(initialValue.length()); // Move cursor to end.
|
||||||
Logger.printException(() -> "Copy settings failure", ex);
|
|
||||||
}
|
// Create custom dialog.
|
||||||
});
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
|
context,
|
||||||
|
getTitle() != null ? getTitle().toString() : "", // Title.
|
||||||
|
null, // Message is replaced by EditText.
|
||||||
|
editText, // Pass the EditText.
|
||||||
|
null, // OK button text.
|
||||||
|
() -> {
|
||||||
|
// OK button action. Persist the EditText value when OK is clicked.
|
||||||
|
String newValue = editText.getText().toString();
|
||||||
|
if (callChangeListener(newValue)) {
|
||||||
|
setText(newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
str("revanced_sb_settings_copy"), // Neutral button text (Copy).
|
||||||
|
() -> {
|
||||||
|
// Neutral button action (Copy).
|
||||||
|
try {
|
||||||
|
Utils.setClipboard(getEditText().getText());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Copy settings failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set dialog as cancelable.
|
||||||
|
dialogPair.first.setCancelable(true);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "showDialog failure", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
privateUserId.setTitle(str("revanced_sb_general_uuid"));
|
privateUserId.setTitle(str("revanced_sb_general_uuid"));
|
||||||
@@ -407,51 +498,90 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
|
|||||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
|
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
|
||||||
editText.setText(Settings.SB_API_URL.get());
|
editText.setText(Settings.SB_API_URL.get());
|
||||||
|
|
||||||
DialogInterface.OnClickListener urlChangeListener = (dialog, buttonPressed) -> {
|
// Create a custom dialog.
|
||||||
if (buttonPressed == DialogInterface.BUTTON_NEUTRAL) {
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
Settings.SB_API_URL.resetToDefault();
|
context,
|
||||||
Utils.showToastLong(str("revanced_sb_api_url_reset"));
|
str("revanced_sb_general_api_url"), // Title.
|
||||||
} else if (buttonPressed == DialogInterface.BUTTON_POSITIVE) {
|
null, // No message, EditText replaces it.
|
||||||
String serverAddress = editText.getText().toString();
|
editText, // Pass the EditText.
|
||||||
if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) {
|
null, // OK button text.
|
||||||
Utils.showToastLong(str("revanced_sb_api_url_invalid"));
|
() -> {
|
||||||
} else if (!serverAddress.equals(Settings.SB_API_URL.get())) {
|
// OK button action.
|
||||||
Settings.SB_API_URL.save(serverAddress);
|
String serverAddress = editText.getText().toString();
|
||||||
Utils.showToastLong(str("revanced_sb_api_url_changed"));
|
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);
|
||||||
new AlertDialog.Builder(context)
|
Utils.showToastLong(str("revanced_sb_api_url_changed"));
|
||||||
.setTitle(apiUrl.getTitle())
|
}
|
||||||
.setView(editText)
|
},
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
() -> {}, // Cancel button action (dismiss dialog).
|
||||||
.setNeutralButton(str("revanced_settings_reset"), urlChangeListener)
|
str("revanced_settings_reset"), // Neutral (Reset) button text.
|
||||||
.setPositiveButton(android.R.string.ok, urlChangeListener)
|
() -> {
|
||||||
.show();
|
// Neutral button action.
|
||||||
|
Settings.SB_API_URL.resetToDefault();
|
||||||
|
Utils.showToastLong(str("revanced_sb_api_url_reset"));
|
||||||
|
},
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
generalCategory.addPreference(apiUrl);
|
generalCategory.addPreference(apiUrl);
|
||||||
|
|
||||||
importExport = new EditTextPreference(context) {
|
importExport = new EditTextPreference(context) {
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
@Override
|
||||||
Utils.setEditTextDialogTheme(builder);
|
protected void showDialog(Bundle state) {
|
||||||
|
try {
|
||||||
|
Context context = getContext();
|
||||||
|
EditText editText = getEditText();
|
||||||
|
|
||||||
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
|
// Create a custom dialog.
|
||||||
try {
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
Utils.setClipboard(getEditText().getText());
|
context,
|
||||||
} catch (Exception ex) {
|
str("revanced_sb_settings_ie"), // Title.
|
||||||
Logger.printException(() -> "Copy settings failure", ex);
|
null, // No message, EditText replaces it.
|
||||||
}
|
editText, // Pass the EditText.
|
||||||
});
|
str("revanced_settings_import"), // OK button text.
|
||||||
|
() -> {
|
||||||
|
// OK button action. Trigger OnPreferenceChangeListener.
|
||||||
|
String newValue = editText.getText().toString();
|
||||||
|
if (getOnPreferenceChangeListener() != null) {
|
||||||
|
getOnPreferenceChangeListener().onPreferenceChange(this, newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
str("revanced_sb_settings_copy"), // Neutral button text (Copy).
|
||||||
|
() -> {
|
||||||
|
// Neutral button action (Copy).
|
||||||
|
try {
|
||||||
|
Utils.setClipboard(editText.getText());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Copy settings failure", ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "showDialog failure", ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
importExport.setTitle(str("revanced_sb_settings_ie"));
|
importExport.setTitle(str("revanced_sb_settings_ie"));
|
||||||
// Summary is set in updateUI()
|
// Summary is set in updateUI().
|
||||||
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
|
EditText editText = importExport.getEditText();
|
||||||
|
editText.setInputType(InputType.TYPE_CLASS_TEXT
|
||||||
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
importExport.getEditText().setAutofillHints((String) null);
|
editText.setAutofillHints((String) null);
|
||||||
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
|
editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
|
||||||
|
|
||||||
|
// Set preference listeners.
|
||||||
importExport.setOnPreferenceClickListener(preference1 -> {
|
importExport.setOnPreferenceClickListener(preference1 -> {
|
||||||
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
|
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package app.revanced.extension.youtube.sponsorblock.ui;
|
|||||||
import static android.text.Html.fromHtml;
|
import static android.text.Html.fromHtml;
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@@ -11,6 +11,8 @@ import android.preference.EditTextPreference;
|
|||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceCategory;
|
import android.preference.PreferenceCategory;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
import android.util.Pair;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@@ -194,14 +196,26 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
|
|||||||
updateStatsSelfSaved.run();
|
updateStatsSelfSaved.run();
|
||||||
|
|
||||||
preference.setOnPreferenceClickListener(preference1 -> {
|
preference.setOnPreferenceClickListener(preference1 -> {
|
||||||
new AlertDialog.Builder(preference1.getContext())
|
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
|
||||||
.setTitle(str("revanced_sb_stats_self_saved_reset_title"))
|
preference.getContext(),
|
||||||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
str("revanced_sb_stats_self_saved_reset_title"), // Title.
|
||||||
|
null, // No message.
|
||||||
|
null, // No EditText.
|
||||||
|
null, // OK button text.
|
||||||
|
() -> {
|
||||||
|
// OK button action.
|
||||||
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault();
|
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault();
|
||||||
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault();
|
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault();
|
||||||
updateStatsSelfSaved.run();
|
updateStatsSelfSaved.run();
|
||||||
})
|
},
|
||||||
.setNegativeButton(android.R.string.no, null).show();
|
() -> {}, // Cancel button action (dismiss only).
|
||||||
|
null, // No neutral button.
|
||||||
|
null, // No neutral button action.
|
||||||
|
true // Dismiss dialog when onNeutralClick.
|
||||||
|
);
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
dialogPair.first.show();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ public class SponsorBlockViewController {
|
|||||||
setSkipButtonMargins(skipSponsorButton, isWatchFullScreen);
|
setSkipButtonMargins(skipSponsorButton, isWatchFullScreen);
|
||||||
setViewVisibility(skipSponsorButton, skipSegment != null);
|
setViewVisibility(skipSponsorButton, skipSegment != null);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Player type changed failure", ex);
|
Logger.printException(() -> "playerTypeChanged failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ public class PlaybackSpeedDialogButton {
|
|||||||
: Settings.PLAYBACK_SPEED_DEFAULT.get();
|
: Settings.PLAYBACK_SPEED_DEFAULT.get();
|
||||||
|
|
||||||
VideoInformation.overridePlaybackSpeed(speed);
|
VideoInformation.overridePlaybackSpeed(speed);
|
||||||
showToastShort(str("revanced_custom_playback_speeds_reset_toast", (speed + "x")));
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "speed button reset failure", ex);
|
Logger.printException(() -> "speed button reset failure", ex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
alias(libs.plugins.android.library)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
|||||||
org.gradle.parallel = true
|
org.gradle.parallel = true
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
kotlin.code.style = official
|
kotlin.code.style = official
|
||||||
version = 5.26.0-dev.5
|
version = 5.32.0-dev.1
|
||||||
|
|||||||
@@ -11,14 +11,25 @@ appcompat = "1.7.0"
|
|||||||
okhttp = "5.0.0-alpha.14"
|
okhttp = "5.0.0-alpha.14"
|
||||||
retrofit = "2.11.0"
|
retrofit = "2.11.0"
|
||||||
guava = "33.4.0-jre"
|
guava = "33.4.0-jre"
|
||||||
|
protobuf-javalite = "4.31.1"
|
||||||
|
protoc = "4.31.1"
|
||||||
|
protobuf = "0.9.5"
|
||||||
|
antlr4 = "4.13.2"
|
||||||
|
nanohttpd = "2.3.1"
|
||||||
|
apksig = "8.10.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||||
|
antlr4 = { module = "org.antlr:antlr4", version.ref = "antlr4" }
|
||||||
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
|
nanohttpd = { module = "org.nanohttpd:nanohttpd", version.ref = "nanohttpd" }
|
||||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||||
|
protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf-javalite" }
|
||||||
|
protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "protoc" }
|
||||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
guava = { module = "com.google.guava:guava", version.ref = "guava" }
|
||||||
|
apksig = { group = "com.android.tools.build", name = "apksig", version.ref = "apksig" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-library = { id = "com.android.library", version.ref = "agp" }
|
android-library = { id = "com.android.library" }
|
||||||
|
protobuf = { id = "com.google.protobuf", version.ref = "protobuf" }
|
||||||
|
|||||||
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,8 +1,6 @@
|
|||||||
|
#Mon Jun 16 14:39:32 CEST 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
|
||||||
networkTimeout=10000
|
|
||||||
validateDistributionUrl=true
|
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
1318
package-lock.json
generated
1318
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user