mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-26 10:54:07 +01:00
Compare commits
75 Commits
v5.13.0-de
...
v5.16.2-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e67f390e2b | ||
|
|
4d910fea93 | ||
|
|
72adbe5519 | ||
|
|
54d49b774e | ||
|
|
283bb31567 | ||
|
|
2724fcbd27 | ||
|
|
7c28193579 | ||
|
|
cd1ee814c4 | ||
|
|
d9ccd73b5f | ||
|
|
5c5a1e4b8b | ||
|
|
66a2ee2416 | ||
|
|
d8c276cf96 | ||
|
|
d5845abd08 | ||
|
|
54eef22ce7 | ||
|
|
e287bdc59d | ||
|
|
20a82ef956 | ||
|
|
1e29da9e06 | ||
|
|
56e6a90a90 | ||
|
|
76d32e21c2 | ||
|
|
54a7afa540 | ||
|
|
ef86438bac | ||
|
|
0683cedac0 | ||
|
|
35753410aa | ||
|
|
df838ed91d | ||
|
|
8e494d26d4 | ||
|
|
7d834e5421 | ||
|
|
60a31cf4e1 | ||
|
|
edb8bd66bc | ||
|
|
04a170054e | ||
|
|
79e6349a69 | ||
|
|
bbf3a34a2f | ||
|
|
1db7c49514 | ||
|
|
ef0506a4f8 | ||
|
|
9b38da35ff | ||
|
|
afdb771066 | ||
|
|
1b2b536d2e | ||
|
|
f39e70c648 | ||
|
|
556acdd9c1 | ||
|
|
7adfc637dc | ||
|
|
9cc0c075ad | ||
|
|
ead11e7f46 | ||
|
|
e9bc201641 | ||
|
|
99baedf355 | ||
|
|
0338d0acd3 | ||
|
|
99879f6e0a | ||
|
|
f0c70de602 | ||
|
|
737ae07a06 | ||
|
|
2c51de59de | ||
|
|
df3dc1c0b2 | ||
|
|
074c948581 | ||
|
|
2a88b1f895 | ||
|
|
ee5c830df8 | ||
|
|
e63a4b31f3 | ||
|
|
8d0bca3b03 | ||
|
|
c162d65d5b | ||
|
|
67dcd091c4 | ||
|
|
ac5ce2d67f | ||
|
|
4b78d056fd | ||
|
|
f8c901b2c1 | ||
|
|
2a67c312e1 | ||
|
|
a7eed30f46 | ||
|
|
e2de2d8d44 | ||
|
|
7ebbf356c0 | ||
|
|
2ced5c6e2a | ||
|
|
4a090ba659 | ||
|
|
cb609a6d9d | ||
|
|
42e6de9e8f | ||
|
|
c4a5b9a28c | ||
|
|
c86c85947f | ||
|
|
cbbf474c50 | ||
|
|
f147b7b73d | ||
|
|
fb8dbb4723 | ||
|
|
1e0d27e689 | ||
|
|
a2185bce09 | ||
|
|
1b60a72ede |
2
.github/workflows/pull_strings.yml
vendored
2
.github/workflows/pull_strings.yml
vendored
@@ -2,7 +2,7 @@ name: Pull strings
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 */6 * * *"
|
||||
- cron: "0 */12 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
238
CHANGELOG.md
238
CHANGELOG.md
@@ -1,3 +1,241 @@
|
||||
## [5.16.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.1...v5.16.2-dev.1) (2025-03-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Facebook - Hide 'Sponsored Stories':** Constrain patch to latest compatible version ([#4657](https://github.com/ReVanced/revanced-patches/issues/4657)) ([46bd1c8](https://github.com/ReVanced/revanced-patches/commit/46bd1c829acd5f83600025e0ceb7d482ae80be69))
|
||||
|
||||
## [5.16.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.0...v5.16.1) (2025-03-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spotify - Unlock Premium:** Override streaming attribute attempting to fix streaming issues ([06be36c](https://github.com/ReVanced/revanced-patches/commit/06be36cddf3430b4179dff696b3d15718cd6963b))
|
||||
|
||||
## [5.16.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.16.0...v5.16.1-dev.1) (2025-03-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Spotify - Unlock Premium:** Override streaming attribute attempting to fix streaming issues ([06be36c](https://github.com/ReVanced/revanced-patches/commit/06be36cddf3430b4179dff696b3d15718cd6963b))
|
||||
|
||||
# [5.16.0](https://github.com/ReVanced/revanced-patches/compare/v5.15.0...v5.16.0) (2025-03-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Settings:** System navigation bar is located above the settings ui on Android 15+ ([f7497be](https://github.com/ReVanced/revanced-patches/commit/f7497be2c5e4abcde6eb55b84955124a28f55cae))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Spotify:** Add `Unlock premium` patch ([#4644](https://github.com/ReVanced/revanced-patches/issues/4644)) ([f048c50](https://github.com/ReVanced/revanced-patches/commit/f048c50e56fc1f5a5c607860be4206ef83b528fe))
|
||||
* **YouTube - Comments:** Add `Hide AI Comments summary` ([#4634](https://github.com/ReVanced/revanced-patches/issues/4634)) ([e9b7f26](https://github.com/ReVanced/revanced-patches/commit/e9b7f263f739bd130f6ea79913851a52355977c5))
|
||||
* **YouTube - Video description:** Add `Hide AI-generated video summary` ([#4636](https://github.com/ReVanced/revanced-patches/issues/4636)) ([521fd48](https://github.com/ReVanced/revanced-patches/commit/521fd48602432ab436d8711c19d7130b2b05af12))
|
||||
|
||||
# [5.16.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.16.0-dev.1...v5.16.0-dev.2) (2025-03-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Spotify:** Add `Unlock premium` patch ([#4644](https://github.com/ReVanced/revanced-patches/issues/4644)) ([f048c50](https://github.com/ReVanced/revanced-patches/commit/f048c50e56fc1f5a5c607860be4206ef83b528fe))
|
||||
|
||||
# [5.16.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.15.0...v5.16.0-dev.1) (2025-03-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Settings:** System navigation bar is located above the settings ui on Android 15+ ([f7497be](https://github.com/ReVanced/revanced-patches/commit/f7497be2c5e4abcde6eb55b84955124a28f55cae))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Comments:** Add `Hide AI Comments summary` ([#4634](https://github.com/ReVanced/revanced-patches/issues/4634)) ([e9b7f26](https://github.com/ReVanced/revanced-patches/commit/e9b7f263f739bd130f6ea79913851a52355977c5))
|
||||
* **YouTube - Video description:** Add `Hide AI-generated video summary` ([#4636](https://github.com/ReVanced/revanced-patches/issues/4636)) ([521fd48](https://github.com/ReVanced/revanced-patches/commit/521fd48602432ab436d8711c19d7130b2b05af12))
|
||||
|
||||
# [5.15.0](https://github.com/ReVanced/revanced-patches/compare/v5.14.0...v5.15.0) (2025-03-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof app version:** Change oldest spoof target to 19.01.34 ([5012439](https://github.com/ReVanced/revanced-patches/commit/5012439a8e53b2a4ab5e85c47976e1ab28a51208))
|
||||
* **YouTube - Spoof app version:** Remove broken spoof targets that YouTube no longer supports ([#4610](https://github.com/ReVanced/revanced-patches/issues/4610)) ([883fbe7](https://github.com/ReVanced/revanced-patches/commit/883fbe71233c57cb1241e57c122b43f40722acc7))
|
||||
* **YouTube:** Do not show restart prompt more than once if setting change is canceled ([49797fe](https://github.com/ReVanced/revanced-patches/commit/49797fe8d0c4a0981ef621a31356c4315ae3777b))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - SponsorBlock:** Add opacity setting to category segment colors ([#4582](https://github.com/ReVanced/revanced-patches/issues/4582)) ([6e8ffba](https://github.com/ReVanced/revanced-patches/commit/6e8ffbade9e03658f725622631e44dabf2995861))
|
||||
|
||||
# [5.15.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.3...v5.15.0-dev.4) (2025-03-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof app version:** Change oldest spoof target to 19.01.34 ([5012439](https://github.com/ReVanced/revanced-patches/commit/5012439a8e53b2a4ab5e85c47976e1ab28a51208))
|
||||
|
||||
# [5.15.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.2...v5.15.0-dev.3) (2025-03-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Do not show restart prompt more than once if setting change is canceled ([49797fe](https://github.com/ReVanced/revanced-patches/commit/49797fe8d0c4a0981ef621a31356c4315ae3777b))
|
||||
|
||||
# [5.15.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.15.0-dev.1...v5.15.0-dev.2) (2025-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof app version:** Remove broken spoof targets that YouTube no longer supports ([#4610](https://github.com/ReVanced/revanced-patches/issues/4610)) ([883fbe7](https://github.com/ReVanced/revanced-patches/commit/883fbe71233c57cb1241e57c122b43f40722acc7))
|
||||
|
||||
# [5.15.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.14.0...v5.15.0-dev.1) (2025-03-19)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - SponsorBlock:** Add opacity setting to category segment colors ([#4582](https://github.com/ReVanced/revanced-patches/issues/4582)) ([6e8ffba](https://github.com/ReVanced/revanced-patches/commit/6e8ffbade9e03658f725622631e44dabf2995861))
|
||||
|
||||
# [5.14.0](https://github.com/ReVanced/revanced-patches/compare/v5.13.0...v5.14.0) (2025-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([5d3c817](https://github.com/ReVanced/revanced-patches/commit/5d3c8175b34a3f6ae2732b25db0851773a8c000d))
|
||||
* **YouTube - Change form factor:** Restore Automotive form factor watch history menu, channel pages, and community posts ([#4541](https://github.com/ReVanced/revanced-patches/issues/4541)) ([aa5c001](https://github.com/ReVanced/revanced-patches/commit/aa5c001968446e5270c756256724e917009612cd))
|
||||
* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([4387a7b](https://github.com/ReVanced/revanced-patches/commit/4387a7b131f49729e902e008bb4cec073635c040))
|
||||
* **YouTube - Hide layout components:** Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled ([62a6164](https://github.com/ReVanced/revanced-patches/commit/62a6164b88b64200b517a5ba6b800d8214dbbad8))
|
||||
* **YouTube - Theme:** Resolve dark mode startup crash with Android 9.0 ([741c2d5](https://github.com/ReVanced/revanced-patches/commit/741c2d59406f5d602554bb3a3c0b8982f42848b4))
|
||||
* **YouTube:** Change language settings menu to use native language names ([#4568](https://github.com/ReVanced/revanced-patches/issues/4568)) ([6f3f8fd](https://github.com/ReVanced/revanced-patches/commit/6f3f8fdce05501e4fa4423c2170a916fbea3b199))
|
||||
* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee67b76](https://github.com/ReVanced/revanced-patches/commit/ee67b763d5c5947a5b1ef4420b1efa820ed6af83))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([cf9f959](https://github.com/ReVanced/revanced-patches/commit/cf9f959923076c10a7f0a29f6ba277f5a055ec07))
|
||||
* **Spotify:** Add `Spoof signature` patch ([#4576](https://github.com/ReVanced/revanced-patches/issues/4576)) ([3646c70](https://github.com/ReVanced/revanced-patches/commit/3646c70556b67a6b7ecf9b86869ebf03c3611333))
|
||||
* **YouTube - Remember video quality:** Add separate Shorts default quality settings ([#4543](https://github.com/ReVanced/revanced-patches/issues/4543)) ([88142ab](https://github.com/ReVanced/revanced-patches/commit/88142ab464192b564b1b8d56a6b45663f77f5e00))
|
||||
|
||||
# [5.14.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.8...v5.14.0-dev.9) (2025-03-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Spotify:** Add `Spoof signature` patch ([#4576](https://github.com/ReVanced/revanced-patches/issues/4576)) ([3646c70](https://github.com/ReVanced/revanced-patches/commit/3646c70556b67a6b7ecf9b86869ebf03c3611333))
|
||||
|
||||
# [5.14.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.7...v5.14.0-dev.8) (2025-03-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Theme:** Resolve dark mode startup crash with Android 9.0 ([741c2d5](https://github.com/ReVanced/revanced-patches/commit/741c2d59406f5d602554bb3a3c0b8982f42848b4))
|
||||
|
||||
# [5.14.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.6...v5.14.0-dev.7) (2025-03-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Change language settings menu to use native language names ([#4568](https://github.com/ReVanced/revanced-patches/issues/4568)) ([6f3f8fd](https://github.com/ReVanced/revanced-patches/commit/6f3f8fdce05501e4fa4423c2170a916fbea3b199))
|
||||
|
||||
# [5.14.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.5...v5.14.0-dev.6) (2025-03-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Do not hide Movie/Courses start page content if 'Hide horizontal shelves' is enabled ([62a6164](https://github.com/ReVanced/revanced-patches/commit/62a6164b88b64200b517a5ba6b800d8214dbbad8))
|
||||
|
||||
# [5.14.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.4...v5.14.0-dev.5) (2025-03-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([cf9f959](https://github.com/ReVanced/revanced-patches/commit/cf9f959923076c10a7f0a29f6ba277f5a055ec07))
|
||||
|
||||
# [5.14.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.3...v5.14.0-dev.4) (2025-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee67b76](https://github.com/ReVanced/revanced-patches/commit/ee67b763d5c5947a5b1ef4420b1efa820ed6af83))
|
||||
|
||||
# [5.14.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.2...v5.14.0-dev.3) (2025-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([5d3c817](https://github.com/ReVanced/revanced-patches/commit/5d3c8175b34a3f6ae2732b25db0851773a8c000d))
|
||||
|
||||
# [5.14.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.1...v5.14.0-dev.2) (2025-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([4387a7b](https://github.com/ReVanced/revanced-patches/commit/4387a7b131f49729e902e008bb4cec073635c040))
|
||||
|
||||
# [5.14.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.1-dev.1...v5.14.0-dev.1) (2025-03-06)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Remember video quality:** Add separate Shorts default quality settings ([#4543](https://github.com/ReVanced/revanced-patches/issues/4543)) ([88142ab](https://github.com/ReVanced/revanced-patches/commit/88142ab464192b564b1b8d56a6b45663f77f5e00))
|
||||
|
||||
## [5.13.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.0...v5.13.1-dev.1) (2025-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Change form factor:** Restore Automotive form factor watch history menu, channel pages, and community posts ([#4541](https://github.com/ReVanced/revanced-patches/issues/4541)) ([aa5c001](https://github.com/ReVanced/revanced-patches/commit/aa5c001968446e5270c756256724e917009612cd))
|
||||
|
||||
# [5.13.0](https://github.com/ReVanced/revanced-patches/compare/v5.12.0...v5.13.0) (2025-03-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **TikTok:** Resolve startup app crash ([18c0fc2](https://github.com/ReVanced/revanced-patches/commit/18c0fc2a7f186f50a904fd25dbaa739abdd24993))
|
||||
* **TikTok:** Resolve startup app crash ([6466398](https://github.com/ReVanced/revanced-patches/commit/64663983b84de1f28636205f61bf0a24c83968d1))
|
||||
* **TikTok:** Resolve startup app crash ([c14bc24](https://github.com/ReVanced/revanced-patches/commit/c14bc244550de30eca975ca7c09e8eb0c47534b5))
|
||||
* **TikTok:** Resolve startup app crash ([d700076](https://github.com/ReVanced/revanced-patches/commit/d7000768a5e5a688c9f4e48858ac34e352222c1e))
|
||||
* **YouTube - Copy video URL:** Use correct button ordering ([5e622cc](https://github.com/ReVanced/revanced-patches/commit/5e622ccf66d34af31c6026fa7f4d332460c6ecb0))
|
||||
* **YouTube - Hide filter bar:** Fix `Hide in feed` not working in subscriptions feed ([#4512](https://github.com/ReVanced/revanced-patches/issues/4512)) ([634d0ee](https://github.com/ReVanced/revanced-patches/commit/634d0ee12e31491c7ee1d4ceb002daf8366a3c15))
|
||||
* **YouTube - Hide layout components:** Do not hide 'Show anyway' button in search results ([4ac8854](https://github.com/ReVanced/revanced-patches/commit/4ac8854b99808a8957f3b0b7438e1e0cdedffbaf))
|
||||
* **YouTube - Hide player components:** Show correct end video thumbnail if `Hide end screen suggested video` is enabled ([#4502](https://github.com/ReVanced/revanced-patches/issues/4502)) ([6c4885a](https://github.com/ReVanced/revanced-patches/commit/6c4885a1d5dfff50100b01840b5552d92e83ee4a))
|
||||
* **YouTube - Hide video action buttons:** Move 'Disable Like and Subscribe glow' to action buttons settings menu ([29b265d](https://github.com/ReVanced/revanced-patches/commit/29b265d8fdaa48502650be9623bfc518a57a0bb1))
|
||||
* **YouTube - Return YouTube Dislike:** Use correct number formatting if using a different ReVanced language ([edf66f4](https://github.com/ReVanced/revanced-patches/commit/edf66f4e16d46156cb8b8e31d18cb8dbcb87737e))
|
||||
* **YouTube - Spoof app version:** Force old settings menus if spoofing to older app targets ([#4490](https://github.com/ReVanced/revanced-patches/issues/4490)) ([45e7c46](https://github.com/ReVanced/revanced-patches/commit/45e7c46dd9c70c926b8b1a97ada668f90f5f6f8c))
|
||||
* **YouTube - Spoof video streams:** Resolve playback issues with dynamic player config ([#4521](https://github.com/ReVanced/revanced-patches/issues/4521)) ([647e764](https://github.com/ReVanced/revanced-patches/commit/647e7642efc0c00db17ccb6a620d1c96ccf4afed))
|
||||
* **YouTube - Swipe controls:** Adjust the overlay text size ([#4503](https://github.com/ReVanced/revanced-patches/issues/4503)) ([6dc4bf7](https://github.com/ReVanced/revanced-patches/commit/6dc4bf75e09ed6f05534919d7b769b720043abce))
|
||||
* **YouTube:** Do not hide player controls when using double tap to skip forward ([#4487](https://github.com/ReVanced/revanced-patches/issues/4487)) ([63fe870](https://github.com/ReVanced/revanced-patches/commit/63fe870d48ca2217327b952bde241b7f16ced850))
|
||||
* **YouTube:** Fix player button fade out animations ([#4469](https://github.com/ReVanced/revanced-patches/issues/4469)) ([bf8e775](https://github.com/ReVanced/revanced-patches/commit/bf8e7759f9bdbdfef419a879fb3dd7cf0dff0098))
|
||||
* **YouTube:** Resolve button flickering when taping seekbar ([#4500](https://github.com/ReVanced/revanced-patches/issues/4500)) ([1f08047](https://github.com/ReVanced/revanced-patches/commit/1f08047b48cc9555a4887d16ec7219a55a77251f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Infinity for Reddit:** Add support for Infinity for Reddit Plus ([#4511](https://github.com/ReVanced/revanced-patches/issues/4511)) ([d74732b](https://github.com/ReVanced/revanced-patches/commit/d74732b7596104321bde263201d95649e4bd0eee))
|
||||
* **NU.nl:** Add `Hide ads` and `Spoof Certificate` patch ([#4368](https://github.com/ReVanced/revanced-patches/issues/4368)) ([f3268fb](https://github.com/ReVanced/revanced-patches/commit/f3268fb03ca25fb5465e36015b6c9dec2c84a655))
|
||||
* **YouTube - Navigation buttons:** Add 'Hide notifications' setting ([#4485](https://github.com/ReVanced/revanced-patches/issues/4485)) ([506d241](https://github.com/ReVanced/revanced-patches/commit/506d2414bbc760e764e5a514b32926083d6ecb6b))
|
||||
* **YouTube - Swipe controls:** Swipe controls UI improvements ([#4422](https://github.com/ReVanced/revanced-patches/issues/4422)) ([198e4d2](https://github.com/ReVanced/revanced-patches/commit/198e4d2a2315c24a09eb9ecfefbd131a75384d2c))
|
||||
|
||||
# [5.13.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.18...v5.13.0-dev.19) (2025-03-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof video streams:** Resolve playback issues with dynamic player config ([#4521](https://github.com/ReVanced/revanced-patches/issues/4521)) ([647e764](https://github.com/ReVanced/revanced-patches/commit/647e7642efc0c00db17ccb6a620d1c96ccf4afed))
|
||||
|
||||
# [5.13.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.17...v5.13.0-dev.18) (2025-02-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Infinity for Reddit:** Add support for Infinity for Reddit Plus ([#4511](https://github.com/ReVanced/revanced-patches/issues/4511)) ([d74732b](https://github.com/ReVanced/revanced-patches/commit/d74732b7596104321bde263201d95649e4bd0eee))
|
||||
|
||||
# [5.13.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.16...v5.13.0-dev.17) (2025-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide filter bar:** Fix `Hide in feed` not working in subscriptions feed ([#4512](https://github.com/ReVanced/revanced-patches/issues/4512)) ([634d0ee](https://github.com/ReVanced/revanced-patches/commit/634d0ee12e31491c7ee1d4ceb002daf8366a3c15))
|
||||
|
||||
# [5.13.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.15...v5.13.0-dev.16) (2025-02-27)
|
||||
|
||||
|
||||
|
||||
@@ -362,27 +362,18 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static void setContext(Context appContext) {
|
||||
// Must initially set context as the language settings needs it.
|
||||
// Must initially set context to check the app language.
|
||||
context = appContext;
|
||||
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
|
||||
|
||||
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||
if (language != AppLanguage.DEFAULT) {
|
||||
// Create a new context with the desired language.
|
||||
Logger.printDebug(() -> "Using app language: " + language);
|
||||
Configuration config = appContext.getResources().getConfiguration();
|
||||
config.setLocale(language.getLocale());
|
||||
context = appContext.createConfigurationContext(config);
|
||||
}
|
||||
|
||||
// In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies.
|
||||
// Calling the regular printDebug method here can cause a Settings context null pointer exception,
|
||||
// even though the context is already set before the call.
|
||||
//
|
||||
// The initialization logger methods do not directly or indirectly
|
||||
// reference the Context or any Settings and are unaffected by this problem.
|
||||
//
|
||||
// Info level also helps debug if a patch hook is called before
|
||||
// the context is set since debug logging is off by default.
|
||||
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
|
||||
}
|
||||
|
||||
public static void setClipboard(@NonNull String text) {
|
||||
|
||||
@@ -8,6 +8,9 @@ public enum AppLanguage {
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
// Languages codes not included with YouTube, but are translated on Crowdin
|
||||
GA,
|
||||
|
||||
// Language codes found in locale_config.xml
|
||||
// All region specific variants have been removed.
|
||||
AF,
|
||||
|
||||
@@ -22,12 +22,23 @@ import app.revanced.extension.shared.settings.Setting;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
/**
|
||||
* Indicates that if a preference changes,
|
||||
* to apply the change from the Setting to the UI component.
|
||||
*/
|
||||
public static boolean settingImportInProgress;
|
||||
|
||||
/**
|
||||
* Prevents recursive calls during preference <-> UI syncing from showing extra dialogs.
|
||||
*/
|
||||
private static boolean updatingPreference;
|
||||
|
||||
/**
|
||||
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
|
||||
*/
|
||||
private static boolean showingUserDialogMessage;
|
||||
|
||||
/**
|
||||
* Confirm and restart dialog button text and title.
|
||||
* Set by subclasses if Strings cannot be added as a resource.
|
||||
@@ -35,13 +46,13 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
@Nullable
|
||||
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle;
|
||||
|
||||
/**
|
||||
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
|
||||
*/
|
||||
private boolean showingUserDialogMessage;
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||
try {
|
||||
if (updatingPreference) {
|
||||
Logger.printDebug(() -> "Ignoring preference change as sync is in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
|
||||
if (setting == null) {
|
||||
return;
|
||||
@@ -63,10 +74,13 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
updatingPreference = true;
|
||||
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
||||
// Updating here can can cause a recursive call back into this same method.
|
||||
updatePreference(pref, setting, true, settingImportInProgress);
|
||||
// Update any other preference availability that may now be different.
|
||||
updateUIAvailability();
|
||||
updatingPreference = false;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||
}
|
||||
@@ -97,7 +111,9 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
if (confirmDialogTitle == null) {
|
||||
confirmDialogTitle = str("revanced_settings_confirm_user_dialog_title");
|
||||
}
|
||||
|
||||
showingUserDialogMessage = true;
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(confirmDialogTitle)
|
||||
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
|
||||
@@ -141,14 +157,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
* @return If the preference is currently set to the default value of the Setting.
|
||||
*/
|
||||
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
|
||||
Object defaultValue = setting.defaultValue;
|
||||
if (pref instanceof SwitchPreference switchPref) {
|
||||
return switchPref.isChecked() == (Boolean) setting.defaultValue;
|
||||
return switchPref.isChecked() == (Boolean) defaultValue;
|
||||
}
|
||||
String defaultValueString = defaultValue.toString();
|
||||
if (pref instanceof EditTextPreference editPreference) {
|
||||
return editPreference.getText().equals(setting.defaultValue.toString());
|
||||
return editPreference.getText().equals(defaultValueString);
|
||||
}
|
||||
if (pref instanceof ListPreference listPref) {
|
||||
return listPref.getValue().equals(setting.defaultValue.toString());
|
||||
return listPref.getValue().equals(defaultValueString);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Must override method to handle "
|
||||
@@ -158,16 +176,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
/**
|
||||
* Syncs all UI Preferences to any {@link Setting} they represent.
|
||||
*/
|
||||
private void updatePreferenceScreen(@NonNull PreferenceScreen screen,
|
||||
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
|
||||
boolean syncSettingValue,
|
||||
boolean applySettingToPreference) {
|
||||
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
|
||||
// but there are many more Settings than UI preferences so it's more efficient to only check
|
||||
// the Preferences.
|
||||
for (int i = 0, prefCount = screen.getPreferenceCount(); i < prefCount; i++) {
|
||||
Preference pref = screen.getPreference(i);
|
||||
if (pref instanceof PreferenceScreen) {
|
||||
updatePreferenceScreen((PreferenceScreen) pref, syncSettingValue, applySettingToPreference);
|
||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||
Preference pref = group.getPreference(i);
|
||||
if (pref instanceof PreferenceGroup subGroup) {
|
||||
updatePreferenceScreen(subGroup, syncSettingValue, applySettingToPreference);
|
||||
} else if (pref.hasKey()) {
|
||||
String key = pref.getKey();
|
||||
Setting<?> setting = Setting.getSettingFromPath(key);
|
||||
@@ -255,7 +273,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
public static void showRestartDialog(@NonNull final Context context) {
|
||||
public static void showRestartDialog(Context context) {
|
||||
Utils.verifyOnMainThread();
|
||||
if (restartDialogTitle == null) {
|
||||
restartDialogTitle = str("revanced_settings_restart_title");
|
||||
@@ -263,6 +281,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
if (restartDialogButtonText == null) {
|
||||
restartDialogButtonText = str("revanced_settings_restart");
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setMessage(restartDialogTitle)
|
||||
.setPositiveButton(restartDialogButtonText, (dialog, id)
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
/**
|
||||
* Empty preference category with no title, used to organize and group related preferences together.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class NoTitlePreferenceCategory extends PreferenceCategory {
|
||||
|
||||
public NoTitlePreferenceCategory(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public NoTitlePreferenceCategory(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("MissingSuperCall")
|
||||
protected View onCreateView(ViewGroup parent) {
|
||||
// Return an zero-height view to eliminate empty title space.
|
||||
return new View(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getTitle() {
|
||||
// Title can be used for sorting. Return the first sub preference title.
|
||||
if (getPreferenceCount() > 0) {
|
||||
return getPreference(0).getTitle();
|
||||
}
|
||||
|
||||
return super.getTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTitleRes() {
|
||||
if (getPreferenceCount() > 0) {
|
||||
return getPreference(0).getTitleRes();
|
||||
}
|
||||
|
||||
return super.getTitleRes();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package app.revanced.extension.shared.settings.preference;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
@@ -8,17 +10,23 @@ import android.util.AttributeSet;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class ResettableEditTextPreference extends EditTextPreference {
|
||||
|
||||
/**
|
||||
* Setting to reset.
|
||||
*/
|
||||
@Nullable
|
||||
private Setting<?> setting;
|
||||
|
||||
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
@@ -32,12 +40,22 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public void setSetting(@Nullable Setting<?> setting) {
|
||||
this.setting = setting;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
Utils.setEditTextDialogTheme(builder);
|
||||
|
||||
Setting<?> setting = Setting.getSettingFromPath(getKey());
|
||||
if (setting == null) {
|
||||
String key = getKey();
|
||||
if (key != null) {
|
||||
setting = Setting.getSettingFromPath(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (setting != null) {
|
||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
||||
}
|
||||
@@ -54,8 +72,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
||||
}
|
||||
button.setOnClickListener(v -> {
|
||||
try {
|
||||
Setting<?> setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
||||
String defaultStringValue = setting.defaultValue.toString();
|
||||
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
|
||||
EditText editText = getEditText();
|
||||
editText.setText(defaultStringValue);
|
||||
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
||||
|
||||
@@ -107,6 +107,21 @@ public class SpoofVideoStreamsPatch {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Turns off a feature flag that interferes with spoofing.
|
||||
*/
|
||||
public static boolean useMediaFetchHotConfigReplacement(boolean original) {
|
||||
if (original) {
|
||||
Logger.printDebug(() -> "useMediaFetchHotConfigReplacement is set on");
|
||||
}
|
||||
|
||||
if (!SPOOF_STREAMING_DATA) {
|
||||
return original;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
|
||||
3
extensions/spotify/build.gradle.kts
Normal file
3
extensions/spotify/build.gradle.kts
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
compileOnly(project(":extensions:spotify:stub"))
|
||||
}
|
||||
1
extensions/spotify/src/main/AndroidManifest.xml
Normal file
1
extensions/spotify/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
17
extensions/spotify/stub/build.gradle.kts
Normal file
17
extensions/spotify/stub/build.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
||||
plugins {
|
||||
id(libs.plugins.android.library.get().pluginId)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
}
|
||||
1
extensions/spotify/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/spotify/stub/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
||||
<manifest/>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.spotify.remoteconfig.internal;
|
||||
|
||||
public final class AccountAttribute {
|
||||
public Object value_;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class BackgroundPlaybackPatch {
|
||||
@@ -23,16 +23,7 @@ public class BackgroundPlaybackPatch {
|
||||
// 7. Close the Short
|
||||
// 8. Resume playing the regular video
|
||||
// 9. Minimize the app (PIP should appear)
|
||||
if (!VideoInformation.lastVideoIdIsShort()) {
|
||||
return true; // Definitely is not a Short.
|
||||
}
|
||||
|
||||
// TODO: Add better hook.
|
||||
// Might be a Shorts, or might be a prior regular video on screen again after a Shorts was closed.
|
||||
// This incorrectly prevents PIP if player is in WATCH_WHILE_MINIMIZED after closing a Shorts,
|
||||
// But there's no way around this unless an additional hook is added to definitively detect
|
||||
// the Shorts player is on screen. This use case is unusual anyways so it's not a huge concern.
|
||||
return !PlayerType.getCurrent().isNoneHiddenOrMinimized();
|
||||
return !ShortsPlayerState.isOpen();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.NavigationBar;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ChangeFormFactorPatch {
|
||||
@@ -41,14 +49,57 @@ public class ChangeFormFactorPatch {
|
||||
|
||||
@Nullable
|
||||
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
|
||||
private static final boolean USING_AUTOMOTIVE_TYPE = Objects.requireNonNull(
|
||||
FormFactor.AUTOMOTIVE.formFactorType).equals(FORM_FACTOR_TYPE);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getFormFactor(int original) {
|
||||
return FORM_FACTOR_TYPE == null
|
||||
? original
|
||||
: FORM_FACTOR_TYPE;
|
||||
if (FORM_FACTOR_TYPE == null) return original;
|
||||
|
||||
if (USING_AUTOMOTIVE_TYPE) {
|
||||
// Do not change if the player is opening or is opened,
|
||||
// otherwise the video description cannot be opened.
|
||||
PlayerType current = PlayerType.getCurrent();
|
||||
if (current.isMaximizedOrFullscreen() || current == PlayerType.WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED) {
|
||||
Logger.printDebug(() -> "Using original form factor for player");
|
||||
return original;
|
||||
}
|
||||
|
||||
if (!NavigationBar.isSearchBarActive()) {
|
||||
// Automotive type shows error 400 when opening a channel page and using some explore tab.
|
||||
// This is a bug in unpatched YouTube that occurs on actual Android Automotive devices.
|
||||
// Work around the issue by using the original form factor if not in search and the
|
||||
// navigation back button is present.
|
||||
if (NavigationBar.isBackButtonVisible()) {
|
||||
Logger.printDebug(() -> "Using original form factor, as back button is visible without search present");
|
||||
return original;
|
||||
}
|
||||
|
||||
// Do not change library tab otherwise watch history is hidden.
|
||||
// Do this check last since the current navigation button is required.
|
||||
if (NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) {
|
||||
return original;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FORM_FACTOR_TYPE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
||||
// On first startup of the app the navigation buttons are fetched and updated.
|
||||
// If the user immediately opens the 'You' or opens a video, then the call to
|
||||
// update the navigtation buttons will use the non automotive form factor
|
||||
// and the explore tab is missing.
|
||||
// Fixing this is not so simple because of the concurrent calls for the player and You tab.
|
||||
// For now, always hide the explore tab.
|
||||
if (USING_AUTOMOTIVE_TYPE && button == NavigationButton.EXPLORE) {
|
||||
tabView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class DisableAutoCaptionsPatch {
|
||||
@@ -14,7 +14,7 @@ public class DisableAutoCaptionsPatch {
|
||||
public static boolean autoCaptionsEnabled() {
|
||||
return Settings.AUTO_CAPTIONS.get()
|
||||
// Do not use auto captions for Shorts.
|
||||
&& !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized();
|
||||
&& ShortsPlayerState.isOpen();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public final class EnableDebuggingPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) {
|
||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
|
||||
if (LOG_FEATURE_FLAGS && value) {
|
||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package app.revanced.extension.youtube.patches;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.shared.PlayerType;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
import app.revanced.extension.youtube.shared.VideoState;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@@ -24,4 +27,26 @@ public class PlayerTypeHookPatch {
|
||||
|
||||
VideoState.setFromString(youTubeVideoState.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* Add a listener to the shorts player overlay View.
|
||||
* Triggered when a shorts player is attached or detached to Windows.
|
||||
*
|
||||
* @param view shorts player overlay (R.id.reel_watch_player).
|
||||
*/
|
||||
public static void onShortsCreate(View view) {
|
||||
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(@Nullable View v) {
|
||||
ShortsPlayerState.setOpen(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(@Nullable View v) {
|
||||
ShortsPlayerState.setOpen(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.util.Objects;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilterPatch;
|
||||
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
@@ -47,9 +46,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
||||
@SuppressWarnings("unused")
|
||||
public class ReturnYouTubeDislikePatch {
|
||||
|
||||
public static final boolean IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER =
|
||||
SpoofAppVersionPatch.isSpoofingToLessThan("18.34.00");
|
||||
|
||||
/**
|
||||
* RYD data for the current video on screen.
|
||||
*/
|
||||
@@ -347,137 +343,6 @@ public class ReturnYouTubeDislikePatch {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Non litho Shorts player.
|
||||
//
|
||||
|
||||
/**
|
||||
* Replacement text to use for "Dislikes" while RYD is fetching.
|
||||
*/
|
||||
private static final Spannable SHORTS_LOADING_SPAN = new SpannableString("-");
|
||||
|
||||
/**
|
||||
* Dislikes TextViews used by Shorts.
|
||||
*
|
||||
* Multiple TextViews are loaded at once (for the prior and next videos to swipe to).
|
||||
* Keep track of all of them, and later pick out the correct one based on their on screen position.
|
||||
*/
|
||||
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();
|
||||
|
||||
private static void clearRemovedShortsTextViews() {
|
||||
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point. Called when a Shorts dislike is updated. Always on main thread.
|
||||
* Handles update asynchronously, otherwise Shorts video will be frozen while the UI thread is blocked.
|
||||
*
|
||||
* @return if RYD is enabled and the TextView was updated.
|
||||
*/
|
||||
public static boolean setShortsDislikes(@NonNull View likeDislikeView) {
|
||||
try {
|
||||
if (!Settings.RYD_ENABLED.get()) {
|
||||
return false;
|
||||
}
|
||||
if (!Settings.RYD_SHORTS.get() || Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) {
|
||||
// Must clear the data here, in case a new video was loaded while PlayerType
|
||||
// suggested the video was not a short (can happen when spoofing to an old app version).
|
||||
clearData();
|
||||
return false;
|
||||
}
|
||||
Logger.printDebug(() -> "setShortsDislikes");
|
||||
|
||||
TextView textView = (TextView) likeDislikeView;
|
||||
textView.setText(SHORTS_LOADING_SPAN); // Change 'Dislike' text to the loading text.
|
||||
shortsTextViewRefs.add(new WeakReference<>(textView));
|
||||
|
||||
if (likeDislikeView.isSelected() && isShortTextViewOnScreen(textView)) {
|
||||
Logger.printDebug(() -> "Shorts dislike is already selected");
|
||||
ReturnYouTubeDislike videoData = currentVideoData;
|
||||
if (videoData != null) videoData.setUserVote(Vote.DISLIKE);
|
||||
}
|
||||
|
||||
// For the first short played, the Shorts dislike hook is called after the video id hook.
|
||||
// But for most other times this hook is called before the video id (which is not ideal).
|
||||
// Must update the TextViews here, and also after the videoId changes.
|
||||
updateOnScreenShortsTextViews(false);
|
||||
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setShortsDislikes failure", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param forceUpdate if false, then only update the 'loading text views.
|
||||
* If true, update all on screen text views.
|
||||
*/
|
||||
private static void updateOnScreenShortsTextViews(boolean forceUpdate) {
|
||||
try {
|
||||
clearRemovedShortsTextViews();
|
||||
if (shortsTextViewRefs.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
ReturnYouTubeDislike videoData = currentVideoData;
|
||||
if (videoData == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "updateShortsTextViews");
|
||||
|
||||
Runnable update = () -> {
|
||||
Spanned shortsDislikesSpan = videoData.getDislikeSpanForShort(SHORTS_LOADING_SPAN);
|
||||
Utils.runOnMainThreadNowOrLater(() -> {
|
||||
String videoId = videoData.getVideoId();
|
||||
if (!videoId.equals(VideoInformation.getVideoId())) {
|
||||
// User swiped to new video before fetch completed
|
||||
Logger.printDebug(() -> "Ignoring stale dislikes data for short: " + videoId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update text views that appear to be visible on screen.
|
||||
// Only 1 will be the actual textview for the current Short,
|
||||
// but discarded and not yet garbage collected views can remain.
|
||||
// So must set the dislike span on all views that match.
|
||||
for (WeakReference<TextView> textViewRef : shortsTextViewRefs) {
|
||||
TextView textView = textViewRef.get();
|
||||
if (textView == null) {
|
||||
continue;
|
||||
}
|
||||
if (isShortTextViewOnScreen(textView)
|
||||
&& (forceUpdate || textView.getText().toString().equals(SHORTS_LOADING_SPAN.toString()))) {
|
||||
Logger.printDebug(() -> "Setting Shorts TextView to: " + shortsDislikesSpan);
|
||||
textView.setText(shortsDislikesSpan);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
if (videoData.fetchCompleted()) {
|
||||
update.run(); // Network call is completed, no need to wait on background thread.
|
||||
} else {
|
||||
Utils.runOnBackgroundThread(update);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "updateOnScreenShortsTextViews failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a view is within the screen bounds.
|
||||
*/
|
||||
private static boolean isShortTextViewOnScreen(@NonNull View view) {
|
||||
final int[] location = new int[2];
|
||||
view.getLocationInWindow(location);
|
||||
if (location[0] <= 0 && location[1] <= 0) { // Lower bound
|
||||
return false;
|
||||
}
|
||||
Rect windowRect = new Rect();
|
||||
view.getWindowVisibleDisplayFrame(windowRect); // Upper bound
|
||||
return location[0] < windowRect.width() && location[1] < windowRect.height();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Video Id and voting hooks (all players).
|
||||
//
|
||||
@@ -503,8 +368,7 @@ public class ReturnYouTubeDislikePatch {
|
||||
if (videoIdIsShort && (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get())) {
|
||||
return;
|
||||
}
|
||||
final boolean waitForFetchToComplete = !IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
||||
&& videoIdIsShort && !lastPlayerResponseWasShort;
|
||||
final boolean waitForFetchToComplete = videoIdIsShort && !lastPlayerResponseWasShort;
|
||||
|
||||
Logger.printDebug(() -> "Prefetching RYD for video: " + videoId);
|
||||
ReturnYouTubeDislike fetch = ReturnYouTubeDislike.getFetchForVideoId(videoId);
|
||||
@@ -557,12 +421,6 @@ public class ReturnYouTubeDislikePatch {
|
||||
data.setVideoIdIsShort(true);
|
||||
}
|
||||
currentVideoData = data;
|
||||
|
||||
// Current video id hook can be called out of order with the non litho Shorts text view hook.
|
||||
// Must manually update again here.
|
||||
if (isNoneHiddenOrSlidingMinimized) {
|
||||
updateOnScreenShortsTextViews(true);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "newVideoLoaded failure", ex);
|
||||
}
|
||||
|
||||
@@ -74,6 +74,7 @@ public final class AdsFilter extends Filter {
|
||||
"video_display_button_group_layout",
|
||||
"landscape_image_wide_button_layout",
|
||||
"video_display_carousel_button_group_layout",
|
||||
"video_display_full_buttoned_short_dr_layout",
|
||||
"compact_landscape_image_layout", // Tablet layout search results.
|
||||
"text_image_no_button_layout" // Tablet layout search results.
|
||||
);
|
||||
|
||||
@@ -2,20 +2,20 @@ package app.revanced.extension.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.extension.youtube.patches.playback.quality.RestoreOldVideoQualityMenuPatch;
|
||||
import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}.
|
||||
* Abuse LithoFilter for {@link AdvancedVideoQualityMenuPatch}.
|
||||
*/
|
||||
public final class VideoQualityMenuFilterPatch extends Filter {
|
||||
public final class AdvancedVideoQualityMenuFilter extends Filter {
|
||||
// Must be volatile or synchronized, as litho filtering runs off main thread
|
||||
// and this field is then access from the main thread.
|
||||
public static volatile boolean isVideoQualityMenuVisible;
|
||||
|
||||
public VideoQualityMenuFilterPatch() {
|
||||
public AdvancedVideoQualityMenuFilter() {
|
||||
addPathCallbacks(new StringFilterGroup(
|
||||
Settings.RESTORE_OLD_VIDEO_QUALITY_MENU,
|
||||
Settings.ADVANCED_VIDEO_QUALITY_MENU,
|
||||
"quick_quality_sheet_content.eml-js"
|
||||
));
|
||||
}
|
||||
@@ -12,10 +12,12 @@ final class CommentsFilter extends Filter {
|
||||
|
||||
private final StringFilterGroup commentComposer;
|
||||
private final ByteArrayFilterGroup emojiPickerBufferGroup;
|
||||
private final StringFilterGroup filterChipBar;
|
||||
private final ByteArrayFilterGroup aiCommentsSummary;
|
||||
|
||||
public CommentsFilter() {
|
||||
var chatSummary = new StringFilterGroup(
|
||||
Settings.HIDE_COMMENTS_CHAT_SUMMARY,
|
||||
Settings.HIDE_COMMENTS_AI_CHAT_SUMMARY,
|
||||
"live_chat_summary_banner.eml"
|
||||
);
|
||||
|
||||
@@ -58,6 +60,16 @@ final class CommentsFilter extends Filter {
|
||||
"id.comment.quick_emoji.button"
|
||||
);
|
||||
|
||||
filterChipBar = new StringFilterGroup(
|
||||
Settings.HIDE_COMMENTS_AI_SUMMARY,
|
||||
"filter_chip_bar.eml"
|
||||
);
|
||||
|
||||
aiCommentsSummary = new ByteArrayFilterGroup(
|
||||
null,
|
||||
"yt_fill_spark_"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
chatSummary,
|
||||
commentsByMembers,
|
||||
@@ -65,7 +77,8 @@ final class CommentsFilter extends Filter {
|
||||
createAShort,
|
||||
previewComment,
|
||||
thanksButton,
|
||||
commentComposer
|
||||
commentComposer,
|
||||
filterChipBar
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,6 +97,13 @@ final class CommentsFilter extends Filter {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matchedGroup == filterChipBar) {
|
||||
if (aiCommentsSummary.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,11 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
"metadata"
|
||||
);
|
||||
|
||||
final StringFilterGroup aiGeneratedVideoSummarySection = new StringFilterGroup(
|
||||
Settings.HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION,
|
||||
"cell_expandable_metadata.eml"
|
||||
);
|
||||
|
||||
final StringFilterGroup attributesSection = new StringFilterGroup(
|
||||
Settings.HIDE_ATTRIBUTES_SECTION,
|
||||
"gaming_section",
|
||||
@@ -67,6 +72,7 @@ final class DescriptionComponentsFilter extends Filter {
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
aiGeneratedVideoSummarySection,
|
||||
attributesSection,
|
||||
infoCardsSection,
|
||||
howThisWasMadeSection,
|
||||
|
||||
@@ -98,6 +98,11 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
"compact_banner"
|
||||
);
|
||||
|
||||
final var subscriptionsChipBar = new StringFilterGroup(
|
||||
Settings.HIDE_FILTER_BAR_FEED_IN_FEED,
|
||||
"subscriptions_chip_bar"
|
||||
);
|
||||
|
||||
inFeedSurvey = new StringFilterGroup(
|
||||
Settings.HIDE_FEED_SURVEY,
|
||||
"in_feed_survey",
|
||||
@@ -264,6 +269,7 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
singleItemInformationPanel,
|
||||
emergencyBox,
|
||||
subscribersCommunityGuidelines,
|
||||
subscriptionsChipBar,
|
||||
channelGuidelines,
|
||||
audioTrackButton,
|
||||
artistCard,
|
||||
@@ -456,6 +462,12 @@ public final class LayoutComponentsFilter extends Filter {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Do not hide if the navigation back button is visible,
|
||||
// otherwise the content shelves in the YouTube Movie/Courses pages is hidden.
|
||||
if (NavigationBar.isBackButtonVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check navigation button last.
|
||||
// Only filter if the library tab is not selected.
|
||||
// This check is important as the shelf layout is used for the library tab playlists.
|
||||
|
||||
@@ -8,30 +8,30 @@ import android.widget.ListView;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.patches.components.VideoQualityMenuFilterPatch;
|
||||
import app.revanced.extension.youtube.patches.components.AdvancedVideoQualityMenuFilter;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* This patch contains the logic to show the old video quality menu.
|
||||
* This patch contains the logic to always open the advanced video quality menu.
|
||||
* Two methods are required, because the quality menu is a RecyclerView in the new YouTube version
|
||||
* and a ListView in the old one.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class RestoreOldVideoQualityMenuPatch {
|
||||
public final class AdvancedVideoQualityMenuPatch {
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
|
||||
if (!Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get()) return;
|
||||
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
|
||||
|
||||
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
|
||||
try {
|
||||
// Check if the current view is the quality menu.
|
||||
if (!VideoQualityMenuFilterPatch.isVideoQualityMenuVisible || recyclerView.getChildCount() == 0) {
|
||||
if (!AdvancedVideoQualityMenuFilter.isVideoQualityMenuVisible || recyclerView.getChildCount() == 0) {
|
||||
return;
|
||||
}
|
||||
VideoQualityMenuFilterPatch.isVideoQualityMenuVisible = false;
|
||||
AdvancedVideoQualityMenuFilter.isVideoQualityMenuVisible = false;
|
||||
|
||||
ViewParent quickQualityViewParent = Utils.getParentView(recyclerView, 3);
|
||||
if (!(quickQualityViewParent instanceof ViewGroup)) {
|
||||
@@ -39,16 +39,15 @@ public final class RestoreOldVideoQualityMenuPatch {
|
||||
}
|
||||
|
||||
View firstChild = recyclerView.getChildAt(0);
|
||||
if (!(firstChild instanceof ViewGroup)) {
|
||||
if (!(firstChild instanceof ViewGroup firstChildGroup)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ViewGroup advancedQualityParentView = (ViewGroup) firstChild;
|
||||
if (advancedQualityParentView.getChildCount() < 4) {
|
||||
if (firstChildGroup.getChildCount() < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
View advancedQualityView = advancedQualityParentView.getChildAt(3);
|
||||
View advancedQualityView = firstChildGroup.getChildAt(3);
|
||||
if (advancedQualityView == null) {
|
||||
return;
|
||||
}
|
||||
@@ -71,7 +70,7 @@ public final class RestoreOldVideoQualityMenuPatch {
|
||||
* Used to force the creation of the advanced menu item for the Shorts quality flyout.
|
||||
*/
|
||||
public static boolean forceAdvancedVideoQualityMenuCreation(boolean original) {
|
||||
return Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get() || original;
|
||||
return Settings.ADVANCED_VIDEO_QUALITY_MENU.get() || original;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,8 +78,8 @@ public final class RestoreOldVideoQualityMenuPatch {
|
||||
*
|
||||
* Used if spoofing to an old app version, and also used for the Shorts video quality flyout.
|
||||
*/
|
||||
public static void showOldVideoQualityMenu(final ListView listView) {
|
||||
if (!Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get()) return;
|
||||
public static void showAdvancedVideoQualityMenu(ListView listView) {
|
||||
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
|
||||
|
||||
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
|
||||
@Override
|
||||
@@ -12,15 +12,19 @@ import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||
import app.revanced.extension.shared.settings.IntegerSetting;
|
||||
import app.revanced.extension.youtube.patches.VideoInformation;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.shared.ShortsPlayerState;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class RememberVideoQualityPatch {
|
||||
private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
|
||||
private static final IntegerSetting wifiQualitySetting = Settings.VIDEO_QUALITY_DEFAULT_WIFI;
|
||||
private static final IntegerSetting mobileQualitySetting = Settings.VIDEO_QUALITY_DEFAULT_MOBILE;
|
||||
private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI;
|
||||
private static final IntegerSetting videoQualityMobile = Settings.VIDEO_QUALITY_DEFAULT_MOBILE;
|
||||
private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI;
|
||||
private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE;
|
||||
|
||||
private static boolean qualityNeedsUpdating;
|
||||
|
||||
@@ -41,17 +45,29 @@ public class RememberVideoQualityPatch {
|
||||
@Nullable
|
||||
private static List<Integer> videoQualities;
|
||||
|
||||
private static boolean shouldRememberVideoQuality() {
|
||||
BooleanSetting preference = ShortsPlayerState.isOpen() ?
|
||||
Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED
|
||||
: Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED;
|
||||
return preference.get();
|
||||
}
|
||||
|
||||
private static void changeDefaultQuality(int defaultQuality) {
|
||||
String networkTypeMessage;
|
||||
boolean useShortsPreference = ShortsPlayerState.isOpen();
|
||||
if (Utils.getNetworkType() == NetworkType.MOBILE) {
|
||||
mobileQualitySetting.save(defaultQuality);
|
||||
if (useShortsPreference) shortsQualityMobile.save(defaultQuality);
|
||||
else videoQualityMobile.save(defaultQuality);
|
||||
networkTypeMessage = str("revanced_remember_video_quality_mobile");
|
||||
} else {
|
||||
wifiQualitySetting.save(defaultQuality);
|
||||
if (useShortsPreference) shortsQualityWifi.save(defaultQuality);
|
||||
else videoQualityWifi.save(defaultQuality);
|
||||
networkTypeMessage = str("revanced_remember_video_quality_wifi");
|
||||
}
|
||||
Utils.showToastShort(
|
||||
str("revanced_remember_video_quality_toast", networkTypeMessage, (defaultQuality + "p")));
|
||||
Utils.showToastShort(str(
|
||||
useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast",
|
||||
networkTypeMessage, (defaultQuality + "p")
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,9 +78,10 @@ public class RememberVideoQualityPatch {
|
||||
*/
|
||||
public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, Object qInterface, String qIndexMethod) {
|
||||
try {
|
||||
boolean useShortsPreference = ShortsPlayerState.isOpen();
|
||||
final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE
|
||||
? mobileQualitySetting.get()
|
||||
: wifiQualitySetting.get();
|
||||
? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get()
|
||||
: (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get();
|
||||
|
||||
if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
|
||||
return originalQualityIndex; // Nothing to do.
|
||||
@@ -141,17 +158,17 @@ public class RememberVideoQualityPatch {
|
||||
* Injection point. Old quality menu.
|
||||
*/
|
||||
public static void userChangedQuality(int selectedQualityIndex) {
|
||||
if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) return;
|
||||
|
||||
userSelectedQualityIndex = selectedQualityIndex;
|
||||
userChangedDefaultQuality = true;
|
||||
if (shouldRememberVideoQuality()) {
|
||||
userSelectedQualityIndex = selectedQualityIndex;
|
||||
userChangedDefaultQuality = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point. New quality menu.
|
||||
*/
|
||||
public static void userChangedQualityInNewFlyout(int selectedQuality) {
|
||||
if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) return;
|
||||
if (!shouldRememberVideoQuality()) return;
|
||||
|
||||
changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080).
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import java.util.concurrent.*;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.youtube.ThemeHelper;
|
||||
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
|
||||
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
@@ -87,9 +86,6 @@ public class ReturnYouTubeDislike {
|
||||
*/
|
||||
private static final char MIDDLE_SEPARATOR_CHARACTER = '◎'; // 'bullseye'
|
||||
|
||||
private static final boolean IS_SPOOFING_TO_OLD_SEPARATOR_COLOR
|
||||
= SpoofAppVersionPatch.isSpoofingToLessThan("18.10.00");
|
||||
|
||||
/**
|
||||
* Cached lookup of all video ids.
|
||||
*/
|
||||
@@ -184,17 +180,8 @@ public class ReturnYouTubeDislike {
|
||||
* Color of the left and middle separator, based on the color of the right separator.
|
||||
* It's unknown where YT gets the color from, and the values here are approximated by hand.
|
||||
* Ideally, this would be the actual color YT uses at runtime.
|
||||
*
|
||||
* Older versions before the 'Me' library tab use a slightly different color.
|
||||
* If spoofing was previously used and is now turned off,
|
||||
* or an old version was recently upgraded then the old colors are sometimes still used.
|
||||
*/
|
||||
private static int getSeparatorColor() {
|
||||
if (IS_SPOOFING_TO_OLD_SEPARATOR_COLOR) {
|
||||
return ThemeHelper.isDarkTheme()
|
||||
? 0x29AAAAAA // transparent dark gray
|
||||
: 0xFFD9D9D9; // light gray
|
||||
}
|
||||
return ThemeHelper.isDarkTheme()
|
||||
? 0x33FFFFFF
|
||||
: 0xFFD9D9D9;
|
||||
|
||||
@@ -3,7 +3,6 @@ package app.revanced.extension.youtube.settings;
|
||||
import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
import static app.revanced.extension.shared.settings.Setting.Availability;
|
||||
import static app.revanced.extension.shared.settings.Setting.migrateFromOldPreferences;
|
||||
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
||||
@@ -21,7 +20,6 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerT
|
||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_4;
|
||||
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
||||
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
||||
import static app.revanced.extension.youtube.patches.VersionCheckPatch.IS_19_17_OR_GREATER;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||
@@ -38,7 +36,6 @@ import app.revanced.extension.shared.settings.IntegerSetting;
|
||||
import app.revanced.extension.shared.settings.LongSetting;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
|
||||
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
|
||||
@@ -47,11 +44,14 @@ import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
||||
|
||||
public class Settings extends BaseSettings {
|
||||
// Video
|
||||
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
||||
public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
||||
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
|
||||
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
|
||||
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
|
||||
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
|
||||
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_shorts_quality_default_wifi", -2, true);
|
||||
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_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 ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
|
||||
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
||||
// Speed
|
||||
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);
|
||||
@@ -168,14 +168,16 @@ public class Settings extends BaseSettings {
|
||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||
// Comments
|
||||
public static final BooleanSetting HIDE_COMMENTS_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_chat_summary", FALSE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_AI_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_chat_summary", FALSE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_AI_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_summary", FALSE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comments_timestamp_and_emoji_buttons", TRUE);
|
||||
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_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comments_timestamp_and_emoji_buttons", TRUE);
|
||||
// Description
|
||||
public static final BooleanSetting HIDE_AI_GENERATED_VIDEO_SUMMARY_SECTION = new BooleanSetting("revanced_hide_ai_generated_video_summary_section", FALSE);
|
||||
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
|
||||
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
|
||||
public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE);
|
||||
@@ -218,7 +220,7 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
||||
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
||||
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.DEFAULT, true);
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", IS_19_17_OR_GREATER ? "19.26.42" : "17.33.42", true, parent(SPOOF_APP_VERSION));
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION));
|
||||
// Custom filter
|
||||
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
||||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||
@@ -362,52 +364,54 @@ public class Settings extends BaseSettings {
|
||||
public static final BooleanSetting SB_SEEN_GUIDELINES = new BooleanSetting("sb_seen_guidelines", FALSE, false, false);
|
||||
public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color", "#00D400");
|
||||
public static final FloatSetting SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_SELF_PROMO = new StringSetting("sb_selfpromo", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_SELF_PROMO_COLOR = new StringSetting("sb_selfpromo_color", "#FFFF00");
|
||||
public static final FloatSetting SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_INTERACTION = new StringSetting("sb_interaction", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_INTERACTION_COLOR = new StringSetting("sb_interaction_color", "#CC00FF");
|
||||
public static final FloatSetting SB_CATEGORY_INTERACTION_OPACITY = new FloatSetting("sb_interaction_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT = new StringSetting("sb_highlight", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_HIGHLIGHT_COLOR = new StringSetting("sb_highlight_color", "#FF1684");
|
||||
public static final FloatSetting SB_CATEGORY_HIGHLIGHT_OPACITY = new FloatSetting("sb_highlight_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_INTRO = new StringSetting("sb_intro", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_INTRO_COLOR = new StringSetting("sb_intro_color", "#00FFFF");
|
||||
public static final FloatSetting SB_CATEGORY_INTRO_OPACITY = new FloatSetting("sb_intro_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_OUTRO = new StringSetting("sb_outro", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_OUTRO_COLOR = new StringSetting("sb_outro_color", "#0202ED");
|
||||
public static final FloatSetting SB_CATEGORY_OUTRO_OPACITY = new FloatSetting("sb_outro_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_PREVIEW = new StringSetting("sb_preview", IGNORE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_PREVIEW_COLOR = new StringSetting("sb_preview_color", "#008FD6");
|
||||
public static final FloatSetting SB_CATEGORY_PREVIEW_OPACITY = new FloatSetting("sb_preview_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_FILLER = new StringSetting("sb_filler", IGNORE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_FILLER_COLOR = new StringSetting("sb_filler_color", "#7300FF");
|
||||
public static final FloatSetting SB_CATEGORY_FILLER_OPACITY = new FloatSetting("sb_filler_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC = new StringSetting("sb_music_offtopic", MANUAL_SKIP.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_MUSIC_OFFTOPIC_COLOR = new StringSetting("sb_music_offtopic_color", "#FF9900");
|
||||
public static final FloatSetting SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY = new FloatSetting("sb_music_offtopic_opacity", 0.8f);
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF");
|
||||
public static final FloatSetting SB_CATEGORY_UNSUBMITTED_OPACITY = new FloatSetting("sb_unsubmitted_opacity", 1.0f);
|
||||
|
||||
// Deprecated migrations
|
||||
private static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024
|
||||
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE, true);
|
||||
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", FALSE);
|
||||
private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127);
|
||||
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
|
||||
private static final BooleanSetting DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE);
|
||||
private static final BooleanSetting DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
||||
|
||||
static {
|
||||
// region Migration
|
||||
|
||||
// Do _not_ delete this SB private user id migration property until sometime in early 2025.
|
||||
// This is the only setting that cannot be reconfigured if lost,
|
||||
// and more time should be given for users who rarely upgrade.
|
||||
SharedPrefCategory sbPrefs = new SharedPrefCategory("sponsor-block");
|
||||
// Remove the "sb_" prefix, as old settings are saved without it.
|
||||
String key = DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING.key.substring(3);
|
||||
migrateFromOldPreferences(sbPrefs, DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, key);
|
||||
|
||||
migrateOldSettingToNew(DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID);
|
||||
|
||||
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_BUTTONS, HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS);
|
||||
|
||||
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
|
||||
|
||||
migrateOldSettingToNew(DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN, HIDE_END_SCREEN_SUGGESTED_VIDEO);
|
||||
|
||||
migrateOldSettingToNew(DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU, ADVANCED_VIDEO_QUALITY_MENU);
|
||||
|
||||
// Migrate renamed enum.
|
||||
//noinspection deprecation
|
||||
if (MINIPLAYER_TYPE.get() == MiniplayerType.PHONE) {
|
||||
@@ -443,6 +447,12 @@ public class Settings extends BaseSettings {
|
||||
DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.resetToDefault();
|
||||
}
|
||||
|
||||
// Old spoof versions that no longer work.
|
||||
if (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) {
|
||||
Logger.printInfo(() -> "Resetting spoof app version target");
|
||||
SPOOF_APP_VERSION_TARGET.resetToDefault();
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region SB import/export callbacks
|
||||
|
||||
@@ -18,7 +18,6 @@ import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
@@ -74,7 +73,8 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(pairsToSort, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
|
||||
pairsToSort.sort((pair1, pair2)
|
||||
-> pair1.first.compareToIgnoreCase(pair2.first));
|
||||
|
||||
CharSequence[] sortedEntries = new CharSequence[entrySize];
|
||||
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
|
||||
@@ -109,6 +109,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
CustomPlaybackSpeedPatch.initializeListPreference(playbackPreference);
|
||||
}
|
||||
|
||||
sortPreferenceListMenu(Settings.CHANGE_START_PAGE);
|
||||
sortPreferenceListMenu(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE);
|
||||
sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE);
|
||||
} catch (Exception ex) {
|
||||
@@ -137,11 +138,13 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||
.findViewById(android.R.id.content)
|
||||
.getParent();
|
||||
|
||||
// Fix required for 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
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
|
||||
v.setPadding(0, statusInsets.top, 0, 0);
|
||||
Insets navInsets = insets.getInsets(WindowInsets.Type.navigationBars());
|
||||
v.setPadding(0, statusInsets.top, 0, navInsets.bottom);
|
||||
return insets;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.BaseSettings;
|
||||
import app.revanced.extension.youtube.patches.ReturnYouTubeDislikePatch;
|
||||
@@ -85,9 +86,7 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
|
||||
shortsPreference = new SwitchPreference(context);
|
||||
shortsPreference.setChecked(Settings.RYD_SHORTS.get());
|
||||
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
|
||||
String shortsSummary = ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
||||
? str("revanced_ryd_shorts_summary_on")
|
||||
: str("revanced_ryd_shorts_summary_on_disclaimer");
|
||||
String shortsSummary = str("revanced_ryd_shorts_summary_on_disclaimer");
|
||||
shortsPreference.setSummaryOn(shortsSummary);
|
||||
shortsPreference.setSummaryOff(str("revanced_ryd_shorts_summary_off"));
|
||||
shortsPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
@@ -237,6 +236,8 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
|
||||
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"));
|
||||
preferenceScreen.addPreference(statisticPreference);
|
||||
}
|
||||
|
||||
Utils.setPreferenceTitlesToMultiLineIfNeeded(preferenceScreen);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onCreate failure", ex);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import androidx.annotation.Nullable;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.Setting;
|
||||
import app.revanced.extension.shared.settings.preference.ResettableEditTextPreference;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
|
||||
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
||||
@@ -44,8 +45,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
||||
private SwitchPreference showTimeWithoutSegments;
|
||||
private SwitchPreference toastOnConnectionError;
|
||||
|
||||
private EditTextPreference newSegmentStep;
|
||||
private EditTextPreference minSegmentDuration;
|
||||
private ResettableEditTextPreference newSegmentStep;
|
||||
private ResettableEditTextPreference minSegmentDuration;
|
||||
private EditTextPreference privateUserId;
|
||||
private EditTextPreference importExport;
|
||||
private Preference apiUrl;
|
||||
@@ -159,6 +160,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
addAboutCategory(context, preferenceScreen);
|
||||
|
||||
Utils.setPreferenceTitlesToMultiLineIfNeeded(preferenceScreen);
|
||||
|
||||
updateUI();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onCreate failure", ex);
|
||||
@@ -268,7 +271,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
||||
return true;
|
||||
});
|
||||
|
||||
newSegmentStep = new EditTextPreference(context);
|
||||
newSegmentStep = new ResettableEditTextPreference(context);
|
||||
newSegmentStep.setSetting(Settings.SB_CREATE_NEW_SEGMENT_STEP);
|
||||
newSegmentStep.setTitle(str("revanced_sb_general_adjusting"));
|
||||
newSegmentStep.setSummary(str("revanced_sb_general_adjusting_sum"));
|
||||
newSegmentStep.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
@@ -326,7 +330,8 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
||||
});
|
||||
category.addPreference(trackSkips);
|
||||
|
||||
minSegmentDuration = new EditTextPreference(context);
|
||||
minSegmentDuration = new ResettableEditTextPreference(context);
|
||||
minSegmentDuration.setSetting(Settings.SB_SEGMENT_MIN_DURATION);
|
||||
minSegmentDuration.setTitle(str("revanced_sb_general_min_duration"));
|
||||
minSegmentDuration.setSummary(str("revanced_sb_general_min_duration_sum"));
|
||||
minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
@@ -345,7 +350,15 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
||||
});
|
||||
category.addPreference(minSegmentDuration);
|
||||
|
||||
privateUserId = new EditTextPreference(context);
|
||||
privateUserId = new EditTextPreference(context) {
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
Utils.setEditTextDialogTheme(builder);
|
||||
|
||||
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
|
||||
Utils.setClipboard(getEditText().getText().toString());
|
||||
});
|
||||
}
|
||||
};
|
||||
privateUserId.setTitle(str("revanced_sb_general_uuid"));
|
||||
privateUserId.setSummary(str("revanced_sb_general_uuid_sum"));
|
||||
privateUserId.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
@@ -504,7 +517,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
||||
|
||||
if (stats.totalSegmentCountIncludingIgnored > 0) {
|
||||
// If user has not created any segments, there's no reason to set a username.
|
||||
EditTextPreference preference = new EditTextPreference(context);
|
||||
EditTextPreference preference = new ResettableEditTextPreference(context);
|
||||
statsCategory.addPreference(preference);
|
||||
String userName = stats.userName;
|
||||
preference.setTitle(fromHtml(str("revanced_sb_stats_username", userName)));
|
||||
|
||||
@@ -73,6 +73,7 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
||||
if (currentClientType == clientType) {
|
||||
return;
|
||||
}
|
||||
currentClientType = clientType;
|
||||
|
||||
Logger.printDebug(() -> "Updating spoof stream side effects preference");
|
||||
setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get());
|
||||
|
||||
@@ -3,7 +3,9 @@ package app.revanced.extension.youtube.shared;
|
||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@@ -24,12 +26,22 @@ import app.revanced.extension.youtube.settings.Settings;
|
||||
@SuppressWarnings("unused")
|
||||
public final class NavigationBar {
|
||||
|
||||
/**
|
||||
* Interface to call obfuscated methods in AppCompat Toolbar class.
|
||||
*/
|
||||
public interface AppCompatToolbarPatchInterface {
|
||||
Drawable patch_getNavigationIcon();
|
||||
}
|
||||
|
||||
//
|
||||
// Search bar
|
||||
// Search and toolbar.
|
||||
//
|
||||
|
||||
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
||||
|
||||
private static volatile WeakReference<AppCompatToolbarPatchInterface> toolbarResultsRef
|
||||
= new WeakReference<>(null);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@@ -37,6 +49,22 @@ public final class NavigationBar {
|
||||
searchBarResultsRef = new WeakReference<>(searchbarResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setToolbar(FrameLayout layout) {
|
||||
AppCompatToolbarPatchInterface toolbar = Utils.getChildView(layout, false, (view) ->
|
||||
view instanceof AppCompatToolbarPatchInterface
|
||||
);
|
||||
|
||||
if (toolbar == null) {
|
||||
Logger.printException(() -> "Could not find navigation toolbar");
|
||||
return;
|
||||
}
|
||||
|
||||
toolbarResultsRef = new WeakReference<>(toolbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the search bar is on screen. This includes if the player
|
||||
* is on screen and the search results are behind the player (and not visible).
|
||||
@@ -47,8 +75,13 @@ public final class NavigationBar {
|
||||
return searchbarResults != null && searchbarResults.getParent() != null;
|
||||
}
|
||||
|
||||
public static boolean isBackButtonVisible() {
|
||||
AppCompatToolbarPatchInterface toolbar = toolbarResultsRef.get();
|
||||
return toolbar != null && toolbar.patch_getNavigationIcon() != null;
|
||||
}
|
||||
|
||||
//
|
||||
// Navigation bar buttons
|
||||
// Navigation bar buttons.
|
||||
//
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,7 +5,7 @@ import app.revanced.extension.youtube.Event
|
||||
import app.revanced.extension.youtube.patches.VideoInformation
|
||||
|
||||
/**
|
||||
* Main player type.
|
||||
* Regular player type.
|
||||
*/
|
||||
enum class PlayerType {
|
||||
/**
|
||||
@@ -90,8 +90,6 @@ enum class PlayerType {
|
||||
* Does not include the first moment after a short is opened when a regular video is minimized on screen,
|
||||
* or while watching a short with a regular video present on a spoofed 16.x version of YouTube.
|
||||
* To include those situations instead use [isNoneHiddenOrMinimized].
|
||||
*
|
||||
* @see VideoInformation
|
||||
*/
|
||||
fun isNoneOrHidden(): Boolean {
|
||||
return this == NONE || this == HIDDEN
|
||||
@@ -107,8 +105,11 @@ enum class PlayerType {
|
||||
* when spoofing to an old version this will return false even
|
||||
* though a Short is being opened or is on screen (see [isNoneHiddenOrMinimized]).
|
||||
*
|
||||
* Instead of this method, consider using {@link ShortsPlayerState}
|
||||
* which may work better for some situations.
|
||||
*
|
||||
* @return If nothing, a Short, or a regular video is sliding off screen to a dismissed or hidden state.
|
||||
* @see VideoInformation
|
||||
* @see ShortsPlayerState
|
||||
*/
|
||||
fun isNoneHiddenOrSlidingMinimized(): Boolean {
|
||||
return isNoneOrHidden() || this == WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED
|
||||
@@ -125,9 +126,12 @@ enum class PlayerType {
|
||||
* Typically used to detect if a Short is playing when the player cannot be in a minimized state,
|
||||
* such as the user interacting with a button or element of the player.
|
||||
*
|
||||
* Instead of this method, consider using {@link ShortsPlayerState}
|
||||
* which may work better for some situations.
|
||||
*
|
||||
* @return If nothing, a Short, a regular video is sliding off screen to a dismissed or hidden state,
|
||||
* a regular video is minimized (and a new video is not being opened).
|
||||
* @see VideoInformation
|
||||
* @see ShortsPlayerState
|
||||
*/
|
||||
fun isNoneHiddenOrMinimized(): Boolean {
|
||||
return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package app.revanced.extension.youtube.shared
|
||||
|
||||
import app.revanced.extension.shared.Logger
|
||||
import app.revanced.extension.youtube.Event
|
||||
|
||||
/**
|
||||
* Shorts player state.
|
||||
*/
|
||||
class ShortsPlayerState {
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun setOpen(open: Boolean) {
|
||||
if (isOpen != open) {
|
||||
Logger.printDebug { "ShortsPlayerState open changed to: $isOpen" }
|
||||
isOpen = open
|
||||
onChange(open)
|
||||
}
|
||||
}
|
||||
|
||||
@Volatile
|
||||
private var isOpen = false
|
||||
|
||||
/**
|
||||
* Shorts player state change listener.
|
||||
*/
|
||||
@JvmStatic
|
||||
val onChange = Event<Boolean>()
|
||||
|
||||
/**
|
||||
* If the Shorts player is currently open.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun isOpen(): Boolean {
|
||||
return isOpen
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ public class SponsorBlockSettings {
|
||||
for (SegmentCategory category : categories) {
|
||||
JSONObject categoryObject = new JSONObject();
|
||||
String categoryKey = category.keyValue;
|
||||
categoryObject.put("color", category.colorString());
|
||||
categoryObject.put("color", category.getColorString());
|
||||
barTypesObject.put(categoryKey, categoryObject);
|
||||
|
||||
if (category.behaviour != CategoryBehaviour.IGNORE) {
|
||||
|
||||
@@ -5,7 +5,12 @@ import static app.revanced.extension.shared.StringRef.str;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.text.Html;
|
||||
import android.graphics.Color;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -33,7 +38,7 @@ import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockViewController
|
||||
* Not thread safe. All fields/methods must be accessed from the main thread.
|
||||
*/
|
||||
public class SponsorBlockUtils {
|
||||
private static final String LOCKED_COLOR = "#FFC83D";
|
||||
private static final int LOCKED_COLOR = Color.parseColor("#FFC83D");
|
||||
private static final String MANUAL_EDIT_TIME_TEXT_HINT = "hh:mm:ss.sss";
|
||||
private static final Pattern manualEditTimePattern
|
||||
= Pattern.compile("((\\d{1,2}):)?(\\d{1,2}):(\\d{2})(\\.(\\d{1,3}))?");
|
||||
@@ -160,32 +165,34 @@ public class SponsorBlockUtils {
|
||||
SegmentVote[] voteOptions = (segment.category == SegmentCategory.HIGHLIGHT)
|
||||
? SegmentVote.voteTypesWithoutCategoryChange // highlight segments cannot change category
|
||||
: SegmentVote.values();
|
||||
CharSequence[] items = new CharSequence[voteOptions.length];
|
||||
final int voteOptionsLength = voteOptions.length;
|
||||
final boolean userIsVip = Settings.SB_USER_IS_VIP.get();
|
||||
CharSequence[] items = new CharSequence[voteOptionsLength];
|
||||
|
||||
for (int i = 0; i < voteOptions.length; i++) {
|
||||
for (int i = 0; i < voteOptionsLength; i++) {
|
||||
SegmentVote voteOption = voteOptions[i];
|
||||
String title = voteOption.title.toString();
|
||||
if (Settings.SB_USER_IS_VIP.get() && segment.isLocked && voteOption.shouldHighlight) {
|
||||
items[i] = Html.fromHtml(String.format("<font color=\"%s\">%s</font>", LOCKED_COLOR, title));
|
||||
} else {
|
||||
items[i] = title;
|
||||
CharSequence title = voteOption.title.toString();
|
||||
if (userIsVip && segment.isLocked && voteOption.highlightIfVipAndVideoIsLocked) {
|
||||
SpannableString coloredTitle = new SpannableString(title);
|
||||
coloredTitle.setSpan(new ForegroundColorSpan(LOCKED_COLOR),
|
||||
0, title.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
title = coloredTitle;
|
||||
}
|
||||
items[i] = title;
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setItems(items, (dialog1, which1) -> {
|
||||
SegmentVote voteOption = voteOptions[which1];
|
||||
switch (voteOption) {
|
||||
case UPVOTE:
|
||||
case DOWNVOTE:
|
||||
SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption);
|
||||
break;
|
||||
case CATEGORY_CHANGE:
|
||||
onNewCategorySelect(segment, context);
|
||||
break;
|
||||
}
|
||||
})
|
||||
.show();
|
||||
new AlertDialog.Builder(context).setItems(items, (dialog1, which1) -> {
|
||||
SegmentVote voteOption = voteOptions[which1];
|
||||
switch (voteOption) {
|
||||
case UPVOTE:
|
||||
case DOWNVOTE:
|
||||
SBRequester.voteForSegmentOnBackgroundThread(segment, voteOption);
|
||||
break;
|
||||
case CATEGORY_CHANGE:
|
||||
onNewCategorySelect(segment, context);
|
||||
break;
|
||||
}
|
||||
}).show();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "segmentVoteClickListener failure", ex);
|
||||
}
|
||||
@@ -282,7 +289,6 @@ public class SponsorBlockUtils {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final int numberOfSegments = segments.length;
|
||||
CharSequence[] titles = new CharSequence[numberOfSegments];
|
||||
for (int i = 0; i < numberOfSegments; i++) {
|
||||
@@ -290,22 +296,33 @@ public class SponsorBlockUtils {
|
||||
if (segment.category == SegmentCategory.UNSUBMITTED) {
|
||||
continue;
|
||||
}
|
||||
StringBuilder htmlBuilder = new StringBuilder();
|
||||
htmlBuilder.append(String.format("<b><font color=\"#%06X\">⬤</font> %s<br>",
|
||||
segment.category.color, segment.category.title));
|
||||
htmlBuilder.append(formatSegmentTime(segment.start));
|
||||
if (segment.category != SegmentCategory.HIGHLIGHT) {
|
||||
htmlBuilder.append(" to ").append(formatSegmentTime(segment.end));
|
||||
|
||||
SpannableStringBuilder spannableBuilder = new SpannableStringBuilder();
|
||||
|
||||
spannableBuilder.append(segment.category.getTitleWithColorDot());
|
||||
spannableBuilder.append('\n');
|
||||
|
||||
String startTime = formatSegmentTime(segment.start);
|
||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||
spannableBuilder.append(startTime);
|
||||
} else {
|
||||
String toFromString = str("revanced_sb_vote_segment_time_to_from",
|
||||
startTime, formatSegmentTime(segment.end));
|
||||
spannableBuilder.append(toFromString);
|
||||
}
|
||||
htmlBuilder.append("</b>");
|
||||
if (i + 1 != numberOfSegments) // prevents trailing new line after last segment
|
||||
htmlBuilder.append("<br>");
|
||||
titles[i] = Html.fromHtml(htmlBuilder.toString());
|
||||
|
||||
if (i + 1 != numberOfSegments) {
|
||||
// prevents trailing new line after last segment
|
||||
spannableBuilder.append('\n');
|
||||
}
|
||||
|
||||
spannableBuilder.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
|
||||
0, spannableBuilder.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
titles[i] = spannableBuilder;
|
||||
}
|
||||
|
||||
new AlertDialog.Builder(context)
|
||||
.setItems(titles, segmentVoteClickListener)
|
||||
.show();
|
||||
new AlertDialog.Builder(context).setItems(titles, segmentVoteClickListener).show();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onVotingClicked failure", ex);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.objects;
|
||||
|
||||
import static app.revanced.extension.youtube.settings.Settings.*;
|
||||
import static app.revanced.extension.shared.StringRef.sf;
|
||||
import static app.revanced.extension.youtube.settings.Settings.*;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.text.Html;
|
||||
import android.text.Spanned;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -15,43 +16,45 @@ import androidx.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.StringRef;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.shared.settings.FloatSetting;
|
||||
import app.revanced.extension.shared.settings.StringSetting;
|
||||
import app.revanced.extension.youtube.settings.Settings;
|
||||
|
||||
public enum SegmentCategory {
|
||||
SPONSOR("sponsor", sf("revanced_sb_segments_sponsor"), sf("revanced_sb_segments_sponsor_sum"), sf("revanced_sb_skip_button_sponsor"), sf("revanced_sb_skipped_sponsor"),
|
||||
SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR),
|
||||
SB_CATEGORY_SPONSOR, SB_CATEGORY_SPONSOR_COLOR, SB_CATEGORY_SPONSOR_OPACITY),
|
||||
SELF_PROMO("selfpromo", sf("revanced_sb_segments_selfpromo"), sf("revanced_sb_segments_selfpromo_sum"), sf("revanced_sb_skip_button_selfpromo"), sf("revanced_sb_skipped_selfpromo"),
|
||||
SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR),
|
||||
SB_CATEGORY_SELF_PROMO, SB_CATEGORY_SELF_PROMO_COLOR, SB_CATEGORY_SELF_PROMO_OPACITY),
|
||||
INTERACTION("interaction", sf("revanced_sb_segments_interaction"), sf("revanced_sb_segments_interaction_sum"), sf("revanced_sb_skip_button_interaction"), sf("revanced_sb_skipped_interaction"),
|
||||
SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR),
|
||||
SB_CATEGORY_INTERACTION, SB_CATEGORY_INTERACTION_COLOR, SB_CATEGORY_INTERACTION_OPACITY),
|
||||
/**
|
||||
* Unique category that is treated differently than the rest.
|
||||
*/
|
||||
HIGHLIGHT("poi_highlight", sf("revanced_sb_segments_highlight"), sf("revanced_sb_segments_highlight_sum"), sf("revanced_sb_skip_button_highlight"), sf("revanced_sb_skipped_highlight"),
|
||||
SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR),
|
||||
SB_CATEGORY_HIGHLIGHT, SB_CATEGORY_HIGHLIGHT_COLOR, SB_CATEGORY_HIGHLIGHT_OPACITY),
|
||||
INTRO("intro", sf("revanced_sb_segments_intro"), sf("revanced_sb_segments_intro_sum"),
|
||||
sf("revanced_sb_skip_button_intro_beginning"), sf("revanced_sb_skip_button_intro_middle"), sf("revanced_sb_skip_button_intro_end"),
|
||||
sf("revanced_sb_skipped_intro_beginning"), sf("revanced_sb_skipped_intro_middle"), sf("revanced_sb_skipped_intro_end"),
|
||||
SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR),
|
||||
SB_CATEGORY_INTRO, SB_CATEGORY_INTRO_COLOR, SB_CATEGORY_INTRO_OPACITY),
|
||||
OUTRO("outro", sf("revanced_sb_segments_outro"), sf("revanced_sb_segments_outro_sum"), sf("revanced_sb_skip_button_outro"), sf("revanced_sb_skipped_outro"),
|
||||
SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR),
|
||||
SB_CATEGORY_OUTRO, SB_CATEGORY_OUTRO_COLOR, SB_CATEGORY_OUTRO_OPACITY),
|
||||
PREVIEW("preview", sf("revanced_sb_segments_preview"), sf("revanced_sb_segments_preview_sum"),
|
||||
sf("revanced_sb_skip_button_preview_beginning"), sf("revanced_sb_skip_button_preview_middle"), sf("revanced_sb_skip_button_preview_end"),
|
||||
sf("revanced_sb_skipped_preview_beginning"), sf("revanced_sb_skipped_preview_middle"), sf("revanced_sb_skipped_preview_end"),
|
||||
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR),
|
||||
SB_CATEGORY_PREVIEW, SB_CATEGORY_PREVIEW_COLOR, SB_CATEGORY_PREVIEW_OPACITY),
|
||||
FILLER("filler", sf("revanced_sb_segments_filler"), sf("revanced_sb_segments_filler_sum"), sf("revanced_sb_skip_button_filler"), sf("revanced_sb_skipped_filler"),
|
||||
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR),
|
||||
SB_CATEGORY_FILLER, SB_CATEGORY_FILLER_COLOR, SB_CATEGORY_FILLER_OPACITY),
|
||||
MUSIC_OFFTOPIC("music_offtopic", sf("revanced_sb_segments_nomusic"), sf("revanced_sb_segments_nomusic_sum"), sf("revanced_sb_skip_button_nomusic"), sf("revanced_sb_skipped_nomusic"),
|
||||
SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR),
|
||||
SB_CATEGORY_MUSIC_OFFTOPIC, SB_CATEGORY_MUSIC_OFFTOPIC_COLOR, SB_CATEGORY_MUSIC_OFFTOPIC_OPACITY),
|
||||
UNSUBMITTED("unsubmitted", StringRef.empty, StringRef.empty, sf("revanced_sb_skip_button_unsubmitted"), sf("revanced_sb_skipped_unsubmitted"),
|
||||
SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR),;
|
||||
SB_CATEGORY_UNSUBMITTED, SB_CATEGORY_UNSUBMITTED_COLOR, SB_CATEGORY_UNSUBMITTED_OPACITY);
|
||||
|
||||
private static final StringRef skipSponsorTextCompact = sf("revanced_sb_skip_button_compact");
|
||||
private static final StringRef skipSponsorTextCompactHighlight = sf("revanced_sb_skip_button_compact_highlight");
|
||||
@@ -90,12 +93,10 @@ public enum SegmentCategory {
|
||||
mValuesMap.put(value.keyValue, value);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SegmentCategory[] categoriesWithoutUnsubmitted() {
|
||||
return categoriesWithoutUnsubmitted;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static SegmentCategory[] categoriesWithoutHighlights() {
|
||||
return categoriesWithoutHighlights;
|
||||
}
|
||||
@@ -106,7 +107,7 @@ public enum SegmentCategory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Must be called if behavior of any category is changed
|
||||
* Must be called if behavior of any category is changed.
|
||||
*/
|
||||
public static void updateEnabledCategories() {
|
||||
Utils.verifyOnMainThread();
|
||||
@@ -133,32 +134,33 @@ public enum SegmentCategory {
|
||||
updateEnabledCategories();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public final String keyValue;
|
||||
@NonNull
|
||||
public final StringSetting behaviorSetting;
|
||||
@NonNull
|
||||
private final StringSetting colorSetting;
|
||||
public static int applyOpacityToColor(int color, float opacity) {
|
||||
if (opacity < 0 || opacity > 1.0f) {
|
||||
throw new IllegalArgumentException("Invalid opacity: " + opacity);
|
||||
}
|
||||
final int opacityInt = (int) (255 * opacity);
|
||||
return (color & 0x00FFFFFF) | (opacityInt << 24);
|
||||
}
|
||||
|
||||
public final String keyValue;
|
||||
public final StringSetting behaviorSetting; // TODO: Replace with EnumSetting.
|
||||
private final StringSetting colorSetting;
|
||||
private final FloatSetting opacitySetting;
|
||||
|
||||
@NonNull
|
||||
public final StringRef title;
|
||||
@NonNull
|
||||
public final StringRef description;
|
||||
|
||||
/**
|
||||
* Skip button text, if the skip occurs in the first quarter of the video
|
||||
*/
|
||||
@NonNull
|
||||
public final StringRef skipButtonTextBeginning;
|
||||
/**
|
||||
* Skip button text, if the skip occurs in the middle half of the video
|
||||
*/
|
||||
@NonNull
|
||||
public final StringRef skipButtonTextMiddle;
|
||||
/**
|
||||
* Skip button text, if the skip occurs in the last quarter of the video
|
||||
*/
|
||||
@NonNull
|
||||
public final StringRef skipButtonTextEnd;
|
||||
/**
|
||||
* Skipped segment toast, if the skip occurred in the first quarter of the video
|
||||
@@ -179,10 +181,7 @@ public enum SegmentCategory {
|
||||
@NonNull
|
||||
public final Paint paint;
|
||||
|
||||
/**
|
||||
* Value must be changed using {@link #setColor(String)}.
|
||||
*/
|
||||
public int color;
|
||||
private int color;
|
||||
|
||||
/**
|
||||
* Value must be changed using {@link #setBehaviour(CategoryBehaviour)}.
|
||||
@@ -194,17 +193,20 @@ public enum SegmentCategory {
|
||||
SegmentCategory(String keyValue, StringRef title, StringRef description,
|
||||
StringRef skipButtonText,
|
||||
StringRef skippedToastText,
|
||||
StringSetting behavior, StringSetting color) {
|
||||
StringSetting behavior,
|
||||
StringSetting color, FloatSetting opacity) {
|
||||
this(keyValue, title, description,
|
||||
skipButtonText, skipButtonText, skipButtonText,
|
||||
skippedToastText, skippedToastText, skippedToastText,
|
||||
behavior, color);
|
||||
behavior,
|
||||
color, opacity);
|
||||
}
|
||||
|
||||
SegmentCategory(String keyValue, StringRef title, StringRef description,
|
||||
StringRef skipButtonTextBeginning, StringRef skipButtonTextMiddle, StringRef skipButtonTextEnd,
|
||||
StringRef skippedToastBeginning, StringRef skippedToastMiddle, StringRef skippedToastEnd,
|
||||
StringSetting behavior, StringSetting color) {
|
||||
StringSetting behavior,
|
||||
StringSetting color, FloatSetting opacity) {
|
||||
this.keyValue = Objects.requireNonNull(keyValue);
|
||||
this.title = Objects.requireNonNull(title);
|
||||
this.description = Objects.requireNonNull(description);
|
||||
@@ -216,6 +218,7 @@ public enum SegmentCategory {
|
||||
this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd);
|
||||
this.behaviorSetting = Objects.requireNonNull(behavior);
|
||||
this.colorSetting = Objects.requireNonNull(color);
|
||||
this.opacitySetting = Objects.requireNonNull(opacity);
|
||||
this.paint = new Paint();
|
||||
loadFromSettings();
|
||||
}
|
||||
@@ -232,11 +235,14 @@ public enum SegmentCategory {
|
||||
this.behaviour = savedBehavior;
|
||||
|
||||
String colorString = colorSetting.get();
|
||||
final float opacity = opacitySetting.get();
|
||||
try {
|
||||
setColor(colorString);
|
||||
setOpacity(opacity);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Invalid color: " + colorString, ex);
|
||||
Logger.printException(() -> "Invalid color: " + colorString + " opacity: " + opacity, ex);
|
||||
colorSetting.resetToDefault();
|
||||
opacitySetting.resetToDefault();
|
||||
loadFromSettings();
|
||||
}
|
||||
}
|
||||
@@ -245,45 +251,78 @@ public enum SegmentCategory {
|
||||
this.behaviour = Objects.requireNonNull(behaviour);
|
||||
this.behaviorSetting.save(behaviour.reVancedKeyValue);
|
||||
}
|
||||
/**
|
||||
* @return HTML color format string
|
||||
*/
|
||||
@NonNull
|
||||
public String colorString() {
|
||||
return String.format("#%06X", color);
|
||||
}
|
||||
|
||||
public void setColor(@NonNull String colorString) throws IllegalArgumentException {
|
||||
final int color = Color.parseColor(colorString) & 0xFFFFFF;
|
||||
this.color = color;
|
||||
private void updateColor() {
|
||||
color = applyOpacityToColor(color, opacitySetting.get());
|
||||
paint.setColor(color);
|
||||
paint.setAlpha(255);
|
||||
colorSetting.save(colorString); // Save after parsing.
|
||||
}
|
||||
|
||||
public void resetColor() {
|
||||
/**
|
||||
* @param opacity Segment color opacity between [0, 1].
|
||||
*/
|
||||
public void setOpacity(float opacity) throws IllegalArgumentException {
|
||||
if (opacity < 0 || opacity > 1) {
|
||||
throw new IllegalArgumentException("Invalid opacity: " + opacity);
|
||||
}
|
||||
|
||||
opacitySetting.save(opacity);
|
||||
updateColor();
|
||||
}
|
||||
|
||||
public float getOpacity() {
|
||||
return opacitySetting.get();
|
||||
}
|
||||
|
||||
public void resetColorAndOpacity() {
|
||||
setColor(colorSetting.defaultValue);
|
||||
setOpacity(opacitySetting.defaultValue);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static String getCategoryColorDotHTML(int color) {
|
||||
color &= 0xFFFFFF;
|
||||
return String.format("<font color=\"#%06X\">⬤</font>", color);
|
||||
/**
|
||||
* @param colorString Segment color with #RRGGBB format.
|
||||
*/
|
||||
public void setColor(String colorString) throws IllegalArgumentException {
|
||||
color = Color.parseColor(colorString);
|
||||
colorSetting.save(colorString);
|
||||
|
||||
updateColor();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Spanned getCategoryColorDot(int color) {
|
||||
return Html.fromHtml(getCategoryColorDotHTML(color));
|
||||
/**
|
||||
* @return Integer color of #RRGGBB format.
|
||||
*/
|
||||
public int getColorNoOpacity() {
|
||||
return color & 0x00FFFFFF;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Spanned getCategoryColorDot() {
|
||||
/**
|
||||
* @return Hex color string of #RRGGBB format with no opacity level.
|
||||
*/
|
||||
public String getColorString() {
|
||||
return String.format(Locale.US, "#%06X", getColorNoOpacity());
|
||||
}
|
||||
|
||||
private static SpannableString getCategoryColorDotSpan(String text, int color) {
|
||||
SpannableString dotSpan = new SpannableString('⬤' + text);
|
||||
dotSpan.setSpan(new ForegroundColorSpan(color), 0, 1,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return dotSpan;
|
||||
}
|
||||
|
||||
public static SpannableString getCategoryColorDot(int color) {
|
||||
return getCategoryColorDotSpan("", color);
|
||||
}
|
||||
|
||||
public SpannableString getCategoryColorDot() {
|
||||
return getCategoryColorDot(color);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Spanned getTitleWithColorDot() {
|
||||
return Html.fromHtml(getCategoryColorDotHTML(color) + " " + title);
|
||||
public SpannableString getTitleWithColorDot(int categoryColor) {
|
||||
return getCategoryColorDotSpan(" " + title, categoryColor);
|
||||
}
|
||||
|
||||
public SpannableString getTitleWithColorDot() {
|
||||
return getTitleWithColorDot(color);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,7 +330,6 @@ public enum SegmentCategory {
|
||||
* @param videoLength length of the video
|
||||
* @return the skip button text
|
||||
*/
|
||||
@NonNull
|
||||
StringRef getSkipButtonText(long segmentStartTime, long videoLength) {
|
||||
if (Settings.SB_COMPACT_SKIP_BUTTON.get()) {
|
||||
return (this == SegmentCategory.HIGHLIGHT)
|
||||
@@ -300,7 +338,7 @@ public enum SegmentCategory {
|
||||
}
|
||||
|
||||
if (videoLength == 0) {
|
||||
return skipButtonTextBeginning; // video is still loading. Assume it's the beginning
|
||||
return skipButtonTextBeginning; // Video is still loading. Assume it's the beginning.
|
||||
}
|
||||
final float position = segmentStartTime / (float) videoLength;
|
||||
if (position < 0.25f) {
|
||||
@@ -316,10 +354,9 @@ public enum SegmentCategory {
|
||||
* @param videoLength length of the video
|
||||
* @return 'skipped segment' toast message
|
||||
*/
|
||||
@NonNull
|
||||
StringRef getSkippedToastText(long segmentStartTime, long videoLength) {
|
||||
if (videoLength == 0) {
|
||||
return skippedToastBeginning; // video is still loading. Assume it's the beginning
|
||||
return skippedToastBeginning; // Video is still loading. Assume it's the beginning.
|
||||
}
|
||||
final float position = segmentStartTime / (float) videoLength;
|
||||
if (position < 0.25f) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.extension.youtube.sponsorblock.objects;
|
||||
|
||||
import static app.revanced.extension.shared.StringRef.str;
|
||||
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
@@ -11,11 +12,10 @@ import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TableLayout;
|
||||
import android.widget.TableRow;
|
||||
import android.widget.GridLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
@@ -24,27 +24,38 @@ import app.revanced.extension.shared.Utils;
|
||||
@SuppressWarnings("deprecation")
|
||||
public class SegmentCategoryListPreference extends ListPreference {
|
||||
private final SegmentCategory category;
|
||||
private EditText mEditText;
|
||||
private int mClickedDialogEntryIndex;
|
||||
private TextView colorDotView;
|
||||
private EditText colorEditText;
|
||||
private EditText opacityEditText;
|
||||
/**
|
||||
* #RRGGBB
|
||||
*/
|
||||
private int categoryColor;
|
||||
/**
|
||||
* [0, 1]
|
||||
*/
|
||||
private float categoryOpacity;
|
||||
private int selectedDialogEntryIndex;
|
||||
|
||||
public SegmentCategoryListPreference(Context context, SegmentCategory category) {
|
||||
super(context);
|
||||
final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT;
|
||||
this.category = Objects.requireNonNull(category);
|
||||
|
||||
// Edit: Using preferences to sync together multiple pieces
|
||||
// of code together is messy and should be rethought.
|
||||
// of code is messy and should be rethought.
|
||||
setKey(category.behaviorSetting.key);
|
||||
setDefaultValue(category.behaviorSetting.defaultValue);
|
||||
|
||||
final boolean isHighlightCategory = category == SegmentCategory.HIGHLIGHT;
|
||||
setEntries(isHighlightCategory
|
||||
? CategoryBehaviour.getBehaviorDescriptionsWithoutSkipOnce()
|
||||
: CategoryBehaviour.getBehaviorDescriptions());
|
||||
setEntryValues(isHighlightCategory
|
||||
? CategoryBehaviour.getBehaviorKeyValuesWithoutSkipOnce()
|
||||
: CategoryBehaviour.getBehaviorKeyValues());
|
||||
|
||||
setSummary(category.description.toString());
|
||||
updateTitle();
|
||||
|
||||
updateTitleFromCategory();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -52,26 +63,40 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
try {
|
||||
Utils.setEditTextDialogTheme(builder);
|
||||
|
||||
categoryColor = category.getColorNoOpacity();
|
||||
categoryOpacity = category.getOpacity();
|
||||
|
||||
Context context = builder.getContext();
|
||||
TableLayout table = new TableLayout(context);
|
||||
table.setOrientation(LinearLayout.HORIZONTAL);
|
||||
table.setPadding(70, 0, 150, 0);
|
||||
|
||||
TableRow row = new TableRow(context);
|
||||
GridLayout gridLayout = new GridLayout(context);
|
||||
gridLayout.setPadding(70, 0, 150, 0); // Padding for the entire layout.
|
||||
gridLayout.setColumnCount(3);
|
||||
gridLayout.setRowCount(2);
|
||||
|
||||
GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||
gridParams.columnSpec = GridLayout.spec(0); // First column.
|
||||
TextView colorTextLabel = new TextView(context);
|
||||
colorTextLabel.setText(str("revanced_sb_color_dot_label"));
|
||||
row.addView(colorTextLabel);
|
||||
colorTextLabel.setLayoutParams(gridParams);
|
||||
gridLayout.addView(colorTextLabel);
|
||||
|
||||
TextView colorDotView = new TextView(context);
|
||||
colorDotView.setText(category.getCategoryColorDot());
|
||||
colorDotView.setPadding(30, 0, 30, 0);
|
||||
row.addView(colorDotView);
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||
gridParams.columnSpec = GridLayout.spec(1); // Second column.
|
||||
gridParams.setMargins(0, 0, 10, 0);
|
||||
colorDotView = new TextView(context);
|
||||
colorDotView.setLayoutParams(gridParams);
|
||||
gridLayout.addView(colorDotView);
|
||||
updateCategoryColorDot();
|
||||
|
||||
mEditText = new EditText(context);
|
||||
mEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
|
||||
mEditText.setText(category.colorString());
|
||||
mEditText.addTextChangedListener(new TextWatcher() {
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(0); // First row.
|
||||
gridParams.columnSpec = GridLayout.spec(2); // Third column.
|
||||
colorEditText = new EditText(context);
|
||||
colorEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
|
||||
colorEditText.setTextLocale(Locale.US);
|
||||
colorEditText.setText(category.getColorString());
|
||||
colorEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
@@ -81,29 +106,94 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
public void afterTextChanged(Editable edit) {
|
||||
try {
|
||||
String colorString = s.toString();
|
||||
String colorString = edit.toString();
|
||||
final int colorStringLength = colorString.length();
|
||||
|
||||
if (!colorString.startsWith("#")) {
|
||||
s.insert(0, "#"); // recursively calls back into this method
|
||||
edit.insert(0, "#"); // Recursively calls back into this method.
|
||||
return;
|
||||
}
|
||||
if (colorString.length() > 7) {
|
||||
s.delete(7, colorString.length());
|
||||
|
||||
final int maxColorStringLength = 7; // #RRGGBB
|
||||
if (colorStringLength > maxColorStringLength) {
|
||||
edit.delete(maxColorStringLength, colorStringLength);
|
||||
return;
|
||||
}
|
||||
final int color = Color.parseColor(colorString);
|
||||
colorDotView.setText(SegmentCategory.getCategoryColorDot(color));
|
||||
|
||||
categoryColor = Color.parseColor(colorString);
|
||||
updateCategoryColorDot();
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// ignore
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
});
|
||||
mEditText.setLayoutParams(new TableRow.LayoutParams(0, TableRow.LayoutParams.WRAP_CONTENT, 1f));
|
||||
row.addView(mEditText);
|
||||
colorEditText.setLayoutParams(gridParams);
|
||||
gridLayout.addView(colorEditText);
|
||||
|
||||
table.addView(row);
|
||||
builder.setView(table);
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(1); // Second row.
|
||||
gridParams.columnSpec = GridLayout.spec(0, 1); // First and second column.
|
||||
TextView opacityLabel = new TextView(context);
|
||||
opacityLabel.setText(str("revanced_sb_color_opacity_label"));
|
||||
opacityLabel.setLayoutParams(gridParams);
|
||||
gridLayout.addView(opacityLabel);
|
||||
|
||||
gridParams = new GridLayout.LayoutParams();
|
||||
gridParams.rowSpec = GridLayout.spec(1); // Second row.
|
||||
gridParams.columnSpec = GridLayout.spec(2); // Third column.
|
||||
opacityEditText = new EditText(context);
|
||||
opacityEditText.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
opacityEditText.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable edit) {
|
||||
try {
|
||||
String editString = edit.toString();
|
||||
final int opacityStringLength = editString.length();
|
||||
|
||||
final int maxOpacityStringLength = 4; // [0.00, 1.00]
|
||||
if (opacityStringLength > maxOpacityStringLength) {
|
||||
edit.delete(maxOpacityStringLength, opacityStringLength);
|
||||
return;
|
||||
}
|
||||
|
||||
final float opacity = opacityStringLength == 0
|
||||
? 0
|
||||
: Float.parseFloat(editString);
|
||||
if (opacity < 0) {
|
||||
categoryOpacity = 0;
|
||||
edit.replace(0, opacityStringLength, "0");
|
||||
return;
|
||||
} else if (opacity > 1.0f) {
|
||||
categoryOpacity = 1;
|
||||
edit.replace(0, opacityStringLength, "1.0");
|
||||
return;
|
||||
} else if (!editString.endsWith(".")) {
|
||||
// Ignore "0." and "1." until the user finishes entering a valid number.
|
||||
categoryOpacity = opacity;
|
||||
}
|
||||
|
||||
updateCategoryColorDot();
|
||||
} catch (NumberFormatException ex) {
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "Could not parse opacity string", ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
opacityEditText.setLayoutParams(gridParams);
|
||||
gridLayout.addView(opacityEditText);
|
||||
updateOpacityText();
|
||||
|
||||
builder.setView(gridLayout);
|
||||
builder.setTitle(category.title.toString());
|
||||
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
@@ -111,8 +201,8 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
});
|
||||
builder.setNeutralButton(str("revanced_sb_reset_color"), (dialog, which) -> {
|
||||
try {
|
||||
category.resetColor();
|
||||
updateTitle();
|
||||
category.resetColorAndOpacity();
|
||||
updateTitleFromCategory();
|
||||
Utils.showToastShort(str("revanced_sb_color_reset"));
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setNeutralButton failure", ex);
|
||||
@@ -120,8 +210,9 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
|
||||
mClickedDialogEntryIndex = findIndexOfValue(getValue());
|
||||
builder.setSingleChoiceItems(getEntries(), mClickedDialogEntryIndex, (dialog, which) -> mClickedDialogEntryIndex = which);
|
||||
selectedDialogEntryIndex = findIndexOfValue(getValue());
|
||||
builder.setSingleChoiceItems(getEntries(), selectedDialogEntryIndex,
|
||||
(dialog, which) -> selectedDialogEntryIndex = which);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onPrepareDialogBuilder failure", ex);
|
||||
}
|
||||
@@ -130,30 +221,51 @@ public class SegmentCategoryListPreference extends ListPreference {
|
||||
@Override
|
||||
protected void onDialogClosed(boolean positiveResult) {
|
||||
try {
|
||||
if (positiveResult && mClickedDialogEntryIndex >= 0 && getEntryValues() != null) {
|
||||
String value = getEntryValues()[mClickedDialogEntryIndex].toString();
|
||||
if (positiveResult && selectedDialogEntryIndex >= 0 && getEntryValues() != null) {
|
||||
String value = getEntryValues()[selectedDialogEntryIndex].toString();
|
||||
if (callChangeListener(value)) {
|
||||
setValue(value);
|
||||
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
|
||||
SegmentCategory.updateEnabledCategories();
|
||||
}
|
||||
String colorString = mEditText.getText().toString();
|
||||
|
||||
try {
|
||||
if (!colorString.equals(category.colorString())) {
|
||||
String colorString = colorEditText.getText().toString();
|
||||
if (!colorString.equals(category.getColorString()) || categoryOpacity != category.getOpacity()) {
|
||||
category.setColor(colorString);
|
||||
category.setOpacity(categoryOpacity);
|
||||
Utils.showToastShort(str("revanced_sb_color_changed"));
|
||||
}
|
||||
} catch (IllegalArgumentException ex) {
|
||||
Utils.showToastShort(str("revanced_sb_color_invalid"));
|
||||
}
|
||||
updateTitle();
|
||||
|
||||
updateTitleFromCategory();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onDialogClosed failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTitle() {
|
||||
setTitle(category.getTitleWithColorDot());
|
||||
private void applyOpacityToCategoryColor() {
|
||||
categoryColor = applyOpacityToColor(categoryColor, categoryOpacity);
|
||||
}
|
||||
|
||||
private void updateTitleFromCategory() {
|
||||
categoryColor = category.getColorNoOpacity();
|
||||
categoryOpacity = category.getOpacity();
|
||||
applyOpacityToCategoryColor();
|
||||
|
||||
setTitle(category.getTitleWithColorDot(categoryColor));
|
||||
}
|
||||
|
||||
private void updateCategoryColorDot() {
|
||||
applyOpacityToCategoryColor();
|
||||
|
||||
colorDotView.setText(SegmentCategory.getCategoryColorDot(categoryColor));
|
||||
}
|
||||
|
||||
private void updateOpacityText() {
|
||||
opacityEditText.setText(String.format(Locale.US, "%.2f", categoryOpacity));
|
||||
}
|
||||
}
|
||||
@@ -23,12 +23,15 @@ public class SponsorSegment implements Comparable<SponsorSegment> {
|
||||
@NonNull
|
||||
public final StringRef title;
|
||||
public final int apiVoteType;
|
||||
public final boolean shouldHighlight;
|
||||
/**
|
||||
* If the option should be highlighted for VIP users.
|
||||
*/
|
||||
public final boolean highlightIfVipAndVideoIsLocked;
|
||||
|
||||
SegmentVote(@NonNull StringRef title, int apiVoteType, boolean shouldHighlight) {
|
||||
SegmentVote(@NonNull StringRef title, int apiVoteType, boolean highlightIfVipAndVideoIsLocked) {
|
||||
this.title = title;
|
||||
this.apiVoteType = apiVoteType;
|
||||
this.shouldHighlight = shouldHighlight;
|
||||
this.highlightIfVipAndVideoIsLocked = highlightIfVipAndVideoIsLocked;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
|
||||
org.gradle.parallel = true
|
||||
android.useAndroidX = true
|
||||
kotlin.code.style = official
|
||||
version = 5.13.0-dev.16
|
||||
version = 5.16.2-dev.1
|
||||
|
||||
@@ -776,8 +776,8 @@ public final class app/revanced/patches/shared/misc/settings/preference/TextPref
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatchKt {
|
||||
public static final fun spoofVideoStreamsPatch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public static synthetic fun spoofVideoStreamsPatch$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public static final fun spoofVideoStreamsPatch (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
public static synthetic fun spoofVideoStreamsPatch$default (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/shared/misc/spoof/UserAgentClientSpoofPatchKt {
|
||||
@@ -816,6 +816,14 @@ public final class app/revanced/patches/spotify/lite/ondemand/OnDemandPatchKt {
|
||||
public static final fun getOnDemandPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/spotify/misc/UnlockPremiumPatchKt {
|
||||
public static final fun getUnlockPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt {
|
||||
public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/spotify/navbar/PremiumNavbarTabPatchKt {
|
||||
public static final fun getPremiumNavbarTabPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
@@ -1463,6 +1471,10 @@ public final class app/revanced/patches/youtube/video/quality/RememberVideoQuali
|
||||
public static final fun getRememberVideoQualityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/video/quality/VideoQualityPatchKt {
|
||||
public static final fun getVideoQualityPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/youtube/video/speed/PlaybackSpeedPatchKt {
|
||||
public static final fun getPlaybackSpeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ import app.revanced.patches.all.misc.transformation.IMethodCall
|
||||
import app.revanced.patches.all.misc.transformation.filterMapInstruction35c
|
||||
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR_PREFIX =
|
||||
"Lapp/revanced/extension/all/connectivity/wifi/spoof/SpoofWifiPatch"
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "$EXTENSION_CLASS_DESCRIPTOR_PREFIX;"
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofWifiPatch = bytecodePatch(
|
||||
|
||||
@@ -8,9 +8,8 @@ import org.w3c.dom.Element
|
||||
@Suppress("unused")
|
||||
val changeVersionCodePatch = resourcePatch(
|
||||
name = "Change version code",
|
||||
description = "Changes the version code of the app. By default the highest version code is set. " +
|
||||
"This allows older versions of an app to be installed " +
|
||||
"if their version code is set to the same or a higher value and can stop app stores to update the app.",
|
||||
description = "Changes the version code of the app. This will turn off app store updates " +
|
||||
"and allows downgrading an existing app install to an older app version.",
|
||||
use = false,
|
||||
) {
|
||||
val versionCode by intOption(
|
||||
@@ -21,7 +20,8 @@ val changeVersionCodePatch = resourcePatch(
|
||||
"Highest" to Int.MAX_VALUE,
|
||||
),
|
||||
title = "Version code",
|
||||
description = "The version code to use",
|
||||
description = "The version code to use. Using the highest value turns off app store " +
|
||||
"updates and allows downgrading an existing app install to an older app version.",
|
||||
required = true,
|
||||
) { versionCode -> versionCode!! >= 1 }
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
val hideSponsoredStoriesPatch = bytecodePatch(
|
||||
name = "Hide 'Sponsored Stories'",
|
||||
) {
|
||||
compatibleWith("com.facebook.katana")
|
||||
compatibleWith("com.facebook.katana"("490.0.0.63.82"))
|
||||
|
||||
execute {
|
||||
val sponsoredDataModelTemplateMethod = getSponsoredDataModelTemplateFingerprint.originalMethod
|
||||
|
||||
@@ -17,7 +17,7 @@ import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/music/spoof/SpoofClientPatch;"
|
||||
|
||||
// TODO: Replace this patch with spoofVideoStreamsPatch once possible.
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package app.revanced.patches.reddit.customclients.boostforreddit.api
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patches.reddit.customclients.spoofClientPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
val spoofClientPatch = spoofClientPatch(redirectUri = "http://rubenmayayo.com") { clientIdOption ->
|
||||
compatibleWith("com.rubenmayayo.reddit")
|
||||
@@ -23,14 +26,15 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "http://rubenmayayo.com")
|
||||
|
||||
// region Patch user agent.
|
||||
|
||||
// Use a random number as the platform in the user agent string.
|
||||
val platformName = (0..100000).random()
|
||||
val platformParameter = 0
|
||||
|
||||
buildUserAgentFingerprint.method.addInstructions(
|
||||
0,
|
||||
"const-string p$platformParameter, \"$platformName\"",
|
||||
)
|
||||
// Use a random user agent.
|
||||
val randomName = (0..100000).random()
|
||||
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
|
||||
buildUserAgentFingerprint.let {
|
||||
val userAgentTemplateIndex = it.stringMatches!!.first().index
|
||||
val register = it.method.getInstruction<OneRegisterInstruction>(userAgentTemplateIndex).registerA
|
||||
|
||||
it.method.replaceInstruction(userAgentTemplateIndex, "const-string v$register, \"$userAgent\"")
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
@@ -8,7 +8,11 @@ import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
|
||||
|
||||
val spoofClientPatch = spoofClientPatch(redirectUri = "infinity://localhost") { clientIdOption ->
|
||||
compatibleWith("ml.docilealligator.infinityforreddit")
|
||||
compatibleWith(
|
||||
"ml.docilealligator.infinityforreddit",
|
||||
"ml.docilealligator.infinityforreddit.plus",
|
||||
"ml.docilealligator.infinityforreddit.patreon"
|
||||
)
|
||||
|
||||
val clientId by clientIdOption
|
||||
|
||||
|
||||
@@ -11,7 +11,11 @@ val unlockSubscriptionPatch = bytecodePatch(
|
||||
) {
|
||||
dependsOn(spoofClientPatch)
|
||||
|
||||
compatibleWith("ml.docilealligator.infinityforreddit")
|
||||
compatibleWith(
|
||||
"ml.docilealligator.infinityforreddit",
|
||||
"ml.docilealligator.infinityforreddit.plus",
|
||||
"ml.docilealligator.infinityforreddit.patreon"
|
||||
)
|
||||
|
||||
execute {
|
||||
setOf(
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.w3c.dom.Element
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
abstract class BasePreference(
|
||||
val key: String? = null,
|
||||
val titleKey: String = "${key}_title",
|
||||
val titleKey: String? = "${key}_title",
|
||||
val summaryKey: String? = "${key}_summary",
|
||||
val icon: String? = null,
|
||||
val layout: String? = null,
|
||||
@@ -35,7 +35,7 @@ abstract class BasePreference(
|
||||
open fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit): Element =
|
||||
ownerDocument.createElement(tag).apply {
|
||||
key?.let { setAttribute("android:key", it) }
|
||||
setAttribute("android:title", "@string/${titleKey}")
|
||||
titleKey?.let { setAttribute("android:title", "@string/${titleKey}") }
|
||||
summaryKey?.let { addSummary(it) }
|
||||
icon?.let {
|
||||
setAttribute("android:icon", it)
|
||||
|
||||
@@ -17,7 +17,7 @@ import org.w3c.dom.Document
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
open class PreferenceCategory(
|
||||
key: String? = null,
|
||||
titleKey: String = "${key}_title",
|
||||
titleKey: String? = "${key}_title",
|
||||
icon: String? = null,
|
||||
layout: String? = null,
|
||||
sorting: Sorting = Sorting.BY_TITLE,
|
||||
|
||||
@@ -137,3 +137,15 @@ internal val patchIncludedExtensionMethodFingerprint = fingerprint {
|
||||
classDef.type == EXTENSION_CLASS_DESCRIPTOR && method.name == "isPatchIncluded"
|
||||
}
|
||||
}
|
||||
|
||||
// Feature flag that turns on Platypus programming language code compiled to native C++.
|
||||
// This code appears to replace the player config after the streams are loaded.
|
||||
// Flag is present in YouTube 19.34, but is missing Platypus stream replacement code until 19.43.
|
||||
// Flag and Platypus code is also present in newer versions of YouTube Music.
|
||||
internal const val MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG = 45645570L
|
||||
|
||||
internal val mediaFetchHotConfigFingerprint = fingerprint {
|
||||
literal {
|
||||
MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,10 +31,11 @@ internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
|
||||
fun spoofVideoStreamsPatch(
|
||||
block: BytecodePatchBuilder.() -> Unit = {},
|
||||
applyMediaFetchHotConfigChanges: BytecodePatchBuilder.() -> Boolean = { false },
|
||||
executeBlock: BytecodePatchContext.() -> Unit = {},
|
||||
) = bytecodePatch(
|
||||
name = "Spoof video streams",
|
||||
description = "Spoofs the client video streams to fix playback.",
|
||||
description = "Adds options to spoof the client video streams to fix playback.",
|
||||
) {
|
||||
block()
|
||||
|
||||
@@ -238,6 +239,17 @@ fun spoofVideoStreamsPatch(
|
||||
|
||||
// endregion
|
||||
|
||||
// region turn off stream config replacement feature flag.
|
||||
|
||||
if (applyMediaFetchHotConfigChanges()) {
|
||||
mediaFetchHotConfigFingerprint.method.insertFeatureFlagBooleanOverride(
|
||||
MEDIA_FETCH_HOT_CONFIG_FEATURE_FLAG,
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->useMediaFetchHotConfigReplacement(Z)Z"
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
executeBlock()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package app.revanced.patches.spotify.misc
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val accountAttributeFingerprint = fingerprint {
|
||||
custom { _, c -> c.endsWith("internal/AccountAttribute;") }
|
||||
}
|
||||
|
||||
internal val productStateProtoFingerprint = fingerprint {
|
||||
returns("Ljava/util/Map;")
|
||||
custom { _, classDef ->
|
||||
classDef.endsWith("ProductStateProto;")
|
||||
}
|
||||
}
|
||||
|
||||
internal val buildQueryParametersFingerprint = fingerprint {
|
||||
strings("trackRows", "device_type:tablet")
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package app.revanced.patches.spotify.misc.fix
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
|
||||
internal val getAppSignatureFingerprint = fingerprint { strings("Failed to get the application signatures") }
|
||||
@@ -0,0 +1,33 @@
|
||||
package app.revanced.patches.spotify.misc.fix
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
@Suppress("unused")
|
||||
val spoofSignaturePatch = bytecodePatch(
|
||||
name = "Spoof signature",
|
||||
description = "Spoofs the signature of the app to fix various functions of the app.",
|
||||
) {
|
||||
compatibleWith("com.spotify.music")
|
||||
|
||||
execute {
|
||||
getAppSignatureFingerprint.method.apply {
|
||||
val failedToGetSignaturesStringMatch = getAppSignatureFingerprint.stringMatches!!.first()
|
||||
|
||||
val concatSignaturesIndex = indexOfFirstInstructionReversedOrThrow(
|
||||
failedToGetSignaturesStringMatch.index,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
)
|
||||
|
||||
val register = getInstruction<OneRegisterInstruction>(concatSignaturesIndex).registerA
|
||||
|
||||
val expectedSignature = "d6a6dced4a85f24204bf9505ccc1fce114cadb32"
|
||||
|
||||
replaceInstruction(concatSignaturesIndex, "const-string v$register, \"$expectedSignature\"")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package app.revanced.patches.spotify.navbar
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal val addNavBarItemFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
literal { showBottomNavigationItemsTextId }
|
||||
}
|
||||
@@ -1,50 +1,12 @@
|
||||
package app.revanced.patches.spotify.navbar
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
|
||||
internal var showBottomNavigationItemsTextId = -1L
|
||||
private set
|
||||
internal var premiumTabId = -1L
|
||||
private set
|
||||
|
||||
private val premiumNavbarTabResourcePatch = resourcePatch {
|
||||
dependsOn(resourceMappingPatch)
|
||||
|
||||
execute {
|
||||
premiumTabId = resourceMappings["id", "premium_tab"]
|
||||
|
||||
showBottomNavigationItemsTextId = resourceMappings[
|
||||
"bool",
|
||||
"show_bottom_navigation_items_text",
|
||||
]
|
||||
}
|
||||
}
|
||||
import app.revanced.patches.spotify.misc.unlockPremiumPatch
|
||||
|
||||
@Deprecated("Superseded by unlockPremiumPatch", ReplaceWith("unlockPremiumPatch"))
|
||||
@Suppress("unused")
|
||||
val premiumNavbarTabPatch = bytecodePatch(
|
||||
name = "Premium navbar tab",
|
||||
description = "Hides the premium tab from the navigation bar.",
|
||||
) {
|
||||
dependsOn(premiumNavbarTabResourcePatch)
|
||||
|
||||
compatibleWith("com.spotify.music")
|
||||
|
||||
// If the navigation bar item is the premium tab, do not add it.
|
||||
execute {
|
||||
addNavBarItemFingerprint.method.addInstructions(
|
||||
0,
|
||||
"""
|
||||
const v1, $premiumTabId
|
||||
if-ne p5, v1, :continue
|
||||
return-void
|
||||
:continue
|
||||
nop
|
||||
""",
|
||||
)
|
||||
}
|
||||
dependsOn(unlockPremiumPatch)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction22c
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/tiktok/settings/AdPersonalizationActivityHook;"
|
||||
|
||||
val settingsPatch = bytecodePatch(
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/tudortmund/lockscreen/ShowOnLockscreenPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
|
||||
@@ -11,7 +11,7 @@ import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/HideGetPremiumPatch;"
|
||||
|
||||
val hideGetPremiumPatch = bytecodePatch(
|
||||
|
||||
@@ -50,7 +50,7 @@ private val downloadsResourcePatch = resourcePatch {
|
||||
}
|
||||
}
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/DownloadsPatch;"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/DownloadsPatch;"
|
||||
|
||||
internal const val BUTTON_DESCRIPTOR = "Lapp/revanced/extension/youtube/videoplayer/ExternalDownloadButton;"
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
val enableSeekbarTappingPatch = bytecodePatch(
|
||||
name = "Seekbar tapping",
|
||||
description = "Adds an option to enable tap-to-seek on the seekbar of the video player.",
|
||||
name = "Enable tap to seek",
|
||||
description = "Adds an option to enable tap to seek on the seekbar of the video player.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/NavigationButtonsPatch;"
|
||||
|
||||
val navigationButtonsPatch = bytecodePatch(
|
||||
|
||||
@@ -43,7 +43,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
|
||||
val hidePlayerOverlayButtonsPatch = bytecodePatch(
|
||||
name = "Hide player overlay buttons",
|
||||
description = "Adds options to hide the player cast, autoplay, caption button and next/ previous buttons.",
|
||||
description = "Adds options to hide the player Cast, Autoplay, Captions, and Previous & Next buttons.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
|
||||
@@ -6,7 +6,9 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
import app.revanced.patches.youtube.layout.buttons.navigation.navigationButtonsPatch
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.navigation.hookNavigationButtonCreated
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.util.getReference
|
||||
@@ -15,7 +17,7 @@ import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeFormFactorPatch;"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/ChangeFormFactorPatch;"
|
||||
|
||||
@Suppress("unused")
|
||||
val changeFormFactorPatch = bytecodePatch(
|
||||
@@ -26,6 +28,7 @@ val changeFormFactorPatch = bytecodePatch(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
navigationButtonsPatch
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
@@ -50,6 +53,8 @@ val changeFormFactorPatch = bytecodePatch(
|
||||
)
|
||||
)
|
||||
|
||||
hookNavigationButtonCreated(EXTENSION_CLASS_DESCRIPTOR)
|
||||
|
||||
createPlayerRequestBodyWithModelFingerprint.method.apply {
|
||||
val formFactorEnumClass = formFactorEnumConstructorFingerprint.originalClassDef.type
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
|
||||
@Suppress("unused")
|
||||
val hideEndscreenCardsPatch = bytecodePatch(
|
||||
name = "Hide endscreen cards",
|
||||
name = "Hide end screen cards",
|
||||
description = "Adds an option to hide suggested video cards at the end of videos.",
|
||||
) {
|
||||
dependsOn(
|
||||
|
||||
@@ -22,7 +22,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
@Suppress("unused")
|
||||
val hideEndScreenSuggestedVideoPatch = bytecodePatch(
|
||||
name = "Hide end screen suggested video",
|
||||
description = "Adds an option to hide the recommended video at the end of each video.",
|
||||
description = "Adds an option to hide the suggested video at the end of videos.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
|
||||
@@ -14,7 +14,7 @@ import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/DisableFullscreenAmbientModePatch;"
|
||||
|
||||
val disableFullscreenAmbientModePatch = bytecodePatch(
|
||||
|
||||
@@ -142,6 +142,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
PreferenceScreenPreference(
|
||||
key = "revanced_hide_description_components_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_ai_generated_video_summary_section"),
|
||||
SwitchPreference("revanced_hide_attributes_section"),
|
||||
SwitchPreference("revanced_hide_chapters_section"),
|
||||
SwitchPreference("revanced_hide_info_cards_section"),
|
||||
@@ -154,13 +155,14 @@ val hideLayoutComponentsPatch = bytecodePatch(
|
||||
PreferenceScreenPreference(
|
||||
"revanced_comments_screen",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_hide_comments_chat_summary"),
|
||||
SwitchPreference("revanced_hide_comments_ai_chat_summary"),
|
||||
SwitchPreference("revanced_hide_comments_ai_summary"),
|
||||
SwitchPreference("revanced_hide_comments_by_members_header"),
|
||||
SwitchPreference("revanced_hide_comments_section"),
|
||||
SwitchPreference("revanced_hide_comments_create_a_short_button"),
|
||||
SwitchPreference("revanced_hide_comments_timestamp_and_emoji_buttons"),
|
||||
SwitchPreference("revanced_hide_comments_preview_comment"),
|
||||
SwitchPreference("revanced_hide_comments_thanks_button"),
|
||||
SwitchPreference("revanced_hide_comments_timestamp_and_emoji_buttons"),
|
||||
),
|
||||
sorting = PreferenceScreenPreference.Sorting.UNSORTED,
|
||||
),
|
||||
|
||||
@@ -159,7 +159,7 @@ private const val FILTER_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/pat
|
||||
@Suppress("unused")
|
||||
val hideShortsComponentsPatch = bytecodePatch(
|
||||
name = "Hide Shorts components",
|
||||
description = "Adds options to hide components related to YouTube Shorts.",
|
||||
description = "Adds options to hide components related to Shorts.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
|
||||
@@ -134,7 +134,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/
|
||||
@Suppress("unused")
|
||||
val miniplayerPatch = bytecodePatch(
|
||||
name = "Miniplayer",
|
||||
description = "Adds options to change the in app minimized player."
|
||||
description = "Adds options to change the in-app minimized player."
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
|
||||
@@ -98,20 +98,6 @@ internal val rollingNumberTextViewFingerprint = fingerprint {
|
||||
}
|
||||
}
|
||||
|
||||
internal val shortsTextViewFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
parameters("L", "L")
|
||||
opcodes(
|
||||
Opcode.INVOKE_SUPER, // first instruction of method
|
||||
Opcode.IF_NEZ,
|
||||
null,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CHECK_CAST,
|
||||
)
|
||||
}
|
||||
|
||||
internal val textComponentConstructorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.CONSTRUCTOR, AccessFlags.PRIVATE)
|
||||
strings("TextComponent")
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package app.revanced.patches.youtube.layout.returnyoutubedislike
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
@@ -169,51 +167,7 @@ val returnYouTubeDislikePatch = bytecodePatch(
|
||||
|
||||
// endregion
|
||||
|
||||
// region Hook for non-litho Short videos.
|
||||
shortsTextViewFingerprint.method.apply {
|
||||
val insertIndex = shortsTextViewFingerprint.patternMatch!!.endIndex + 1
|
||||
|
||||
// If the field is true, the TextView is for a dislike button.
|
||||
val isDisLikesBooleanInstruction = instructions.first { instruction ->
|
||||
instruction.opcode == Opcode.IGET_BOOLEAN
|
||||
} as ReferenceInstruction
|
||||
|
||||
val isDisLikesBooleanReference = isDisLikesBooleanInstruction.reference
|
||||
|
||||
// Like/Dislike button TextView field.
|
||||
val textViewFieldInstruction = instructions.first { instruction ->
|
||||
instruction.opcode == Opcode.IGET_OBJECT
|
||||
} as ReferenceInstruction
|
||||
|
||||
val textViewFieldReference = textViewFieldInstruction.reference
|
||||
|
||||
// Check if the hooked TextView object is that of the dislike button.
|
||||
// If RYD is disabled, or the TextView object is not that of the dislike button, the execution flow is not interrupted.
|
||||
// Otherwise, the TextView object is modified, and the execution flow is interrupted to prevent it from being changed afterward.
|
||||
addInstructionsWithLabels(
|
||||
insertIndex,
|
||||
"""
|
||||
# Check, if the TextView is for a dislike button
|
||||
iget-boolean v0, p0, $isDisLikesBooleanReference
|
||||
if-eqz v0, :is_like
|
||||
|
||||
# Hook the TextView, if it is for the dislike button
|
||||
iget-object v0, p0, $textViewFieldReference
|
||||
invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->setShortsDislikes(Landroid/view/View;)Z
|
||||
move-result v0
|
||||
if-eqz v0, :ryd_disabled
|
||||
return-void
|
||||
|
||||
:is_like
|
||||
:ryd_disabled
|
||||
nop
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Hook for litho Shorts
|
||||
// region Hook Shorts
|
||||
|
||||
// Filter that parses the video id from the UI
|
||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
@@ -255,22 +209,25 @@ val returnYouTubeDislikePatch = bytecodePatch(
|
||||
)
|
||||
}
|
||||
|
||||
// Rolling Number text views use the measured width of the raw string for layout.
|
||||
// Modify the measure text calculation to include the left drawable separator if needed.
|
||||
val patternMatch = rollingNumberMeasureAnimatedTextFingerprint.patternMatch!!
|
||||
// Additional check to verify the opcodes are at the start of the method
|
||||
if (patternMatch.startIndex != 0) throw PatchException("Unexpected opcode location")
|
||||
val endIndex = patternMatch.endIndex
|
||||
rollingNumberMeasureAnimatedTextFingerprint.method.apply {
|
||||
val measuredTextWidthRegister = getInstruction<OneRegisterInstruction>(endIndex).registerA
|
||||
rollingNumberMeasureAnimatedTextFingerprint.let {
|
||||
// Rolling Number text views use the measured width of the raw string for layout.
|
||||
// Modify the measure text calculation to include the left drawable separator if needed.
|
||||
val patternMatch = it.patternMatch!!
|
||||
// Verify the opcodes are at the start of the method.
|
||||
if (patternMatch.startIndex != 0) throw PatchException("Unexpected opcode location")
|
||||
val endIndex = patternMatch.endIndex
|
||||
|
||||
addInstructions(
|
||||
endIndex + 1,
|
||||
"""
|
||||
invoke-static {p1, v$measuredTextWidthRegister}, $EXTENSION_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F
|
||||
move-result v$measuredTextWidthRegister
|
||||
""",
|
||||
)
|
||||
it.method.apply {
|
||||
val measuredTextWidthRegister = getInstruction<OneRegisterInstruction>(endIndex).registerA
|
||||
|
||||
addInstructions(
|
||||
endIndex + 1,
|
||||
"""
|
||||
invoke-static {p1, v$measuredTextWidthRegister}, $EXTENSION_CLASS_DESCRIPTOR->onRollingNumberMeasured(Ljava/lang/String;F)F
|
||||
move-result v$measuredTextWidthRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Additional text measurement method. Used if YouTube decides not to animate the likes count
|
||||
@@ -291,15 +248,14 @@ val returnYouTubeDislikePatch = bytecodePatch(
|
||||
)
|
||||
}
|
||||
}
|
||||
// The rolling number Span is missing styling since it's initially set as a String.
|
||||
// Modify the UI text view and use the styled like/dislike Span.
|
||||
// Initial TextView is set in this method.
|
||||
val initiallyCreatedTextViewMethod = rollingNumberTextViewFingerprint.method
|
||||
|
||||
// Videos less than 24 hours after uploaded, like counts will be updated in real time.
|
||||
// Whenever like counts are updated, TextView is set in this method.
|
||||
arrayOf(
|
||||
initiallyCreatedTextViewMethod,
|
||||
// The rolling number Span is missing styling since it's initially set as a String.
|
||||
// Modify the UI text view and use the styled like/dislike Span.
|
||||
// Initial TextView is set in this method.
|
||||
rollingNumberTextViewFingerprint.method,
|
||||
// Videos less than 24 hours after uploaded, like counts will be updated in real time.
|
||||
// Whenever like counts are updated, TextView is set in this method.
|
||||
rollingNumberTextViewAnimationUpdateFingerprint.method,
|
||||
).forEach { insertMethod ->
|
||||
insertMethod.apply {
|
||||
@@ -315,9 +271,9 @@ val returnYouTubeDislikePatch = bytecodePatch(
|
||||
addInstructions(
|
||||
setTextIndex,
|
||||
"""
|
||||
invoke-static {v$textViewRegister, v$textSpanRegister}, $EXTENSION_CLASS_DESCRIPTOR->updateRollingNumber(Landroid/widget/TextView;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
|
||||
move-result-object v$textSpanRegister
|
||||
""",
|
||||
invoke-static {v$textViewRegister, v$textSpanRegister}, $EXTENSION_CLASS_DESCRIPTOR->updateRollingNumber(Landroid/widget/TextView;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
|
||||
move-result-object v$textSpanRegister
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/WideSearchbarPatch;"
|
||||
|
||||
val wideSearchbarPatch = bytecodePatch(
|
||||
name = "Wide searchbar",
|
||||
name = "Wide search bar",
|
||||
description = "Adds an option to replace the search icon with a wide search bar. This will hide the YouTube logo when active.",
|
||||
) {
|
||||
dependsOn(
|
||||
|
||||
@@ -12,9 +12,11 @@ import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_17_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
@@ -44,8 +46,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
val spoofAppVersionPatch = bytecodePatch(
|
||||
name = "Spoof app version",
|
||||
description = "Adds an option to trick YouTube into thinking you are running an older version of the app. " +
|
||||
"This can be used to restore old UI elements and features. " +
|
||||
"Patching 19.16.39 includes additional older spoofing targets.",
|
||||
"This can be used to restore old UI elements and features."
|
||||
) {
|
||||
dependsOn(
|
||||
spoofAppVersionResourcePatch,
|
||||
@@ -58,8 +59,8 @@ val spoofAppVersionPatch = bytecodePatch(
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.16.39",
|
||||
// "19.25.37", // Cannot be supported because the lowest spoof target is higher.
|
||||
// "19.34.42", // Cannot be supported because the lowest spoof target is higher.
|
||||
"19.25.37",
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.45.38",
|
||||
"19.46.42",
|
||||
@@ -71,27 +72,35 @@ val spoofAppVersionPatch = bytecodePatch(
|
||||
addResources("youtube", "layout.spoofappversion.spoofAppVersionPatch")
|
||||
|
||||
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
|
||||
SwitchPreference("revanced_spoof_app_version"),
|
||||
if (is_19_17_or_greater) {
|
||||
ListPreference(
|
||||
key = "revanced_spoof_app_version_target",
|
||||
summaryKey = null,
|
||||
// Group the switch and list preference together, since General menu is sorted by name
|
||||
// and the preferences can be scattered apart with non English langauges.
|
||||
PreferenceCategory(
|
||||
titleKey = null,
|
||||
sorting = Sorting.UNSORTED,
|
||||
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
|
||||
preferences = setOf(
|
||||
SwitchPreference("revanced_spoof_app_version"),
|
||||
if (is_19_43_or_greater) {
|
||||
ListPreference(
|
||||
key = "revanced_spoof_app_version_target",
|
||||
summaryKey = null,
|
||||
)
|
||||
} else {
|
||||
ListPreference(
|
||||
key = "revanced_spoof_app_version_target",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_spoof_app_version_target_legacy_entries",
|
||||
entryValuesKey = "revanced_spoof_app_version_target_legacy_entry_values"
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
ListPreference(
|
||||
key = "revanced_spoof_app_version_target",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_spoof_app_version_target_legacy_entries",
|
||||
entryValuesKey = "revanced_spoof_app_version_target_legacy_entry_values"
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
/**
|
||||
* If a user really wants to spoof to very old versions with the latest app target
|
||||
* they can modify the import/export spoof version. But when spoofing the 19.20.xx
|
||||
* or earlier the Library tab can crash due to missing image resources trying to load.
|
||||
* As a temporary workaround, do not set an image in the toolbar when the enum name is UNKNOWN.
|
||||
* If spoofing to target 19.20 or earlier the Library tab can crash due to
|
||||
* missing image resources. As a workaround, do not set an image in the
|
||||
* toolbar when the enum name is UNKNOWN.
|
||||
*/
|
||||
toolBarButtonFingerprint.method.apply {
|
||||
val getDrawableIndex = indexOfGetDrawableInstruction(this)
|
||||
|
||||
@@ -8,7 +8,10 @@ import app.revanced.patcher.patch.stringOption
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
import app.revanced.patches.youtube.layout.seekbar.seekbarColorPatch
|
||||
@@ -71,6 +74,9 @@ val themePatch = bytecodePatch(
|
||||
)
|
||||
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
lithoColorHookPatch,
|
||||
seekbarColorPatch,
|
||||
versionCheckPatch,
|
||||
@@ -78,23 +84,30 @@ val themePatch = bytecodePatch(
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
resourceMappingPatch,
|
||||
addResourcesPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("youtube", "layout.theme.themeResourcePatch")
|
||||
|
||||
PreferenceScreen.SEEKBAR.addPreferences(
|
||||
val preferences = mutableSetOf<BasePreference>(
|
||||
SwitchPreference("revanced_seekbar_custom_color"),
|
||||
TextPreference("revanced_seekbar_custom_color_primary", inputType = InputType.TEXT_CAP_CHARACTERS),
|
||||
)
|
||||
|
||||
if (is_19_25_or_greater) {
|
||||
PreferenceScreen.SEEKBAR.addPreferences(
|
||||
TextPreference("revanced_seekbar_custom_color_accent", inputType = InputType.TEXT_CAP_CHARACTERS),
|
||||
preferences += TextPreference(
|
||||
"revanced_seekbar_custom_color_accent",
|
||||
inputType = InputType.TEXT_CAP_CHARACTERS
|
||||
)
|
||||
}
|
||||
|
||||
PreferenceScreen.SEEKBAR.addPreferences(
|
||||
PreferenceCategory(
|
||||
titleKey = null,
|
||||
sorting = Sorting.UNSORTED,
|
||||
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
|
||||
preferences = preferences
|
||||
)
|
||||
)
|
||||
|
||||
// Edit theme colors via resources.
|
||||
document("res/values/colors.xml").use { document ->
|
||||
|
||||
@@ -125,7 +138,6 @@ val themePatch = bytecodePatch(
|
||||
colorValue: String,
|
||||
) {
|
||||
document(resourceFile).use { document ->
|
||||
|
||||
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
|
||||
|
||||
resourcesNode.appendChild(
|
||||
@@ -133,7 +145,7 @@ val themePatch = bytecodePatch(
|
||||
setAttribute("name", colorName)
|
||||
setAttribute("category", "color")
|
||||
textContent = colorValue
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -152,11 +164,10 @@ val themePatch = bytecodePatch(
|
||||
// Edit splash screen files and change the background color,
|
||||
// if the background colors are set.
|
||||
if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) {
|
||||
val splashScreenResourceFiles =
|
||||
listOf(
|
||||
"res/drawable/quantum_launchscreen_youtube.xml",
|
||||
"res/drawable-sw600dp/quantum_launchscreen_youtube.xml",
|
||||
)
|
||||
val splashScreenResourceFiles = listOf(
|
||||
"res/drawable/quantum_launchscreen_youtube.xml",
|
||||
"res/drawable-sw600dp/quantum_launchscreen_youtube.xml",
|
||||
)
|
||||
|
||||
splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile ->
|
||||
document(resourceFile).use { document ->
|
||||
@@ -174,36 +185,34 @@ val themePatch = bytecodePatch(
|
||||
// Fix the splash screen dark mode background color.
|
||||
// In 19.32+ the dark mode splash screen is white and fades to black.
|
||||
// Maybe it's a bug in YT, or maybe it intentionally. Who knows.
|
||||
document("res/values-night/styles.xml").use { document ->
|
||||
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
|
||||
val childNodes = resourcesNode.childNodes
|
||||
document("res/values-night-v27/styles.xml").use { document ->
|
||||
// Create a night mode specific override for the splash screen background.
|
||||
val style = document.createElement("style")
|
||||
style.setAttribute("name", "Theme.YouTube.Home")
|
||||
style.setAttribute("parent", "@style/Base.V27.Theme.YouTube.Home")
|
||||
|
||||
for (i in 0 until childNodes.length) {
|
||||
val node = childNodes.item(i) as? Element ?: continue
|
||||
val nodeAttributeName = node.getAttribute("name")
|
||||
if (nodeAttributeName.startsWith("Theme.YouTube.Launcher")) {
|
||||
val nodeAttributeParent = node.getAttribute("parent")
|
||||
|
||||
val style = document.createElement("style")
|
||||
style.setAttribute("name", "Theme.YouTube.Home")
|
||||
style.setAttribute("parent", nodeAttributeParent)
|
||||
|
||||
val windowItem = document.createElement("item")
|
||||
windowItem.setAttribute("name", "android:windowBackground")
|
||||
windowItem.textContent = "@color/$splashBackgroundColor"
|
||||
style.appendChild(windowItem)
|
||||
|
||||
resourcesNode.removeChild(node)
|
||||
resourcesNode.appendChild(style)
|
||||
}
|
||||
// Fix status and navigation bar showing white on some Android devices,
|
||||
// such as SDK 28 Android 10 medium tablet.
|
||||
val colorSplashBackgroundColor = "@color/$splashBackgroundColor"
|
||||
arrayOf(
|
||||
"android:navigationBarColor" to colorSplashBackgroundColor,
|
||||
"android:windowBackground" to colorSplashBackgroundColor,
|
||||
"android:colorBackground" to colorSplashBackgroundColor,
|
||||
"colorPrimaryDark" to colorSplashBackgroundColor,
|
||||
"android:windowLightStatusBar" to "false",
|
||||
).forEach { (name, value) ->
|
||||
val styleItem = document.createElement("item")
|
||||
styleItem.setAttribute("name", name)
|
||||
styleItem.textContent = value
|
||||
style.appendChild(styleItem)
|
||||
}
|
||||
|
||||
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
|
||||
resourcesNode.appendChild(style)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
}
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.patches.youtube.misc.debugging
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
@@ -11,9 +12,11 @@ import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.util.findInstructionIndicesReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/EnableDebuggingPatch;"
|
||||
@@ -61,19 +64,17 @@ val enableDebuggingPatch = bytecodePatch(
|
||||
experimentalBooleanFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT)
|
||||
findInstructionIndicesReversedOrThrow(Opcode.RETURN).forEach { index ->
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
// It appears that all usage of this method has a default of 'false',
|
||||
// so there's no need to pass in the default.
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result v0
|
||||
invoke-static { v0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZJ)Z
|
||||
move-result v0
|
||||
return v0
|
||||
"""
|
||||
)
|
||||
addInstructions(
|
||||
index,
|
||||
"""
|
||||
invoke-static { v$register, p1 }, $EXTENSION_CLASS_DESCRIPTOR->isBooleanFeatureFlagEnabled(ZLjava/lang/Long;)Z
|
||||
move-result v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
experimentalDoubleFeatureFlagFingerprint.match(
|
||||
@@ -92,7 +93,6 @@ val enableDebuggingPatch = bytecodePatch(
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
experimentalLongFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
@@ -108,21 +108,22 @@ val enableDebuggingPatch = bytecodePatch(
|
||||
"""
|
||||
)
|
||||
|
||||
experimentalStringFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)
|
||||
}
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result-object v0
|
||||
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
experimentalStringFeatureFlagFingerprint.match(
|
||||
experimentalFeatureFlagParentFingerprint.originalClassDef
|
||||
).method.apply {
|
||||
val insertIndex = indexOfFirstInstructionReversedOrThrow(Opcode.MOVE_RESULT_OBJECT)
|
||||
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
move-result-object v0
|
||||
invoke-static { v0, p1, p2, p3 }, $EXTENSION_CLASS_DESCRIPTOR->isStringFeatureFlagEnabled(Ljava/lang/String;JLjava/lang/String;)Ljava/lang/String;
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
// There exists other experimental accessor methods for byte[]
|
||||
|
||||
@@ -11,9 +11,9 @@ internal val experimentalFeatureFlagParentFingerprint = fingerprint {
|
||||
}
|
||||
|
||||
internal val experimentalBooleanFeatureFlagFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("Z")
|
||||
parameters("J", "Z")
|
||||
parameters("L", "J", "Z")
|
||||
}
|
||||
|
||||
internal val experimentalDoubleFeatureFlagFingerprint = fingerprint {
|
||||
@@ -33,4 +33,3 @@ internal val experimentalStringFeatureFlagFingerprint = fingerprint {
|
||||
returns("Ljava/lang/String;")
|
||||
parameters("J", "Ljava/lang/String;")
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import com.android.tools.smali.dexlib2.iface.reference.StringReference
|
||||
|
||||
val openLinksExternallyPatch = bytecodePatch(
|
||||
name = "Open links externally",
|
||||
description = "Adds an option to always open links in your browser instead of in the in-app-browser.",
|
||||
description = "Adds an option to always open links in your browser instead of the in-app browser.",
|
||||
) {
|
||||
dependsOn(
|
||||
transformInstructionsPatch(
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
lateinit var addLithoFilter: (String) -> Unit
|
||||
private set
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/components/LithoFilterPatch;"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/components/LithoFilterPatch;"
|
||||
|
||||
val lithoFilterPatch = bytecodePatch(
|
||||
description = "Hooks the method which parses the bytes into a ComponentContext to filter components.",
|
||||
|
||||
@@ -16,6 +16,23 @@ internal val actionBarSearchResultsFingerprint = fingerprint {
|
||||
literal { actionBarSearchResultsViewMicId }
|
||||
}
|
||||
|
||||
internal val toolbarLayoutFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PROTECTED, AccessFlags.CONSTRUCTOR)
|
||||
literal { toolbarContainerId }
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches to https://android.googlesource.com/platform/frameworks/support/+/9eee6ba/v7/appcompat/src/android/support/v7/widget/Toolbar.java#963
|
||||
*/
|
||||
internal val appCompatToolbarBackButtonFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("Landroid/graphics/drawable/Drawable;")
|
||||
parameters()
|
||||
custom { methodDef, classDef ->
|
||||
classDef.type == "Landroid/support/v7/widget/Toolbar;"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches to the class found in [pivotBarConstructorFingerprint].
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
@@ -18,12 +19,16 @@ import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.TypeReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
internal var imageOnlyTabResourceId = -1L
|
||||
@@ -32,6 +37,8 @@ internal var actionBarSearchResultsViewMicId = -1L
|
||||
private set
|
||||
internal var ytFillBellId = -1L
|
||||
private set
|
||||
internal var toolbarContainerId = -1L
|
||||
private set
|
||||
|
||||
private val navigationBarHookResourcePatch = resourcePatch {
|
||||
dependsOn(resourceMappingPatch)
|
||||
@@ -40,6 +47,7 @@ private val navigationBarHookResourcePatch = resourcePatch {
|
||||
imageOnlyTabResourceId = resourceMappings["layout", "image_only_tab"]
|
||||
actionBarSearchResultsViewMicId = resourceMappings["layout", "action_bar_search_results_view_mic"]
|
||||
ytFillBellId = resourceMappings["drawable", "yt_fill_bell_black_24"]
|
||||
toolbarContainerId = resourceMappings["id", "toolbar_container"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +55,8 @@ internal const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/shared/NavigationBar;"
|
||||
internal const val EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/shared/NavigationBar\$NavigationButton;"
|
||||
private const val EXTENSION_TOOLBAR_INTERFACE =
|
||||
"Lapp/revanced/extension/youtube/shared/NavigationBar${'$'}AppCompatToolbarPatchInterface;"
|
||||
|
||||
lateinit var hookNavigationButtonCreated: (String) -> Unit
|
||||
|
||||
@@ -143,11 +153,58 @@ val navigationBarHookPatch = bytecodePatch(description = "Hooks the active navig
|
||||
)
|
||||
}
|
||||
|
||||
// Hook the back button visibility.
|
||||
|
||||
toolbarLayoutFingerprint.method.apply {
|
||||
val index = indexOfFirstInstructionOrThrow {
|
||||
opcode == Opcode.CHECK_CAST && getReference<TypeReference>()?.type ==
|
||||
"Lcom/google/android/apps/youtube/app/ui/actionbar/MainCollapsingToolbarLayout;"
|
||||
}
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
addInstruction(
|
||||
index + 1,
|
||||
"invoke-static { v$register }, ${EXTENSION_CLASS_DESCRIPTOR}->setToolbar(Landroid/widget/FrameLayout;)V"
|
||||
)
|
||||
}
|
||||
|
||||
// Add interface for extensions code to call obfuscated methods.
|
||||
appCompatToolbarBackButtonFingerprint.let {
|
||||
it.classDef.apply {
|
||||
interfaces.add(EXTENSION_TOOLBAR_INTERFACE)
|
||||
|
||||
val definingClass = type
|
||||
val obfuscatedMethodName = it.originalMethod.name
|
||||
val returnType = "Landroid/graphics/drawable/Drawable;"
|
||||
|
||||
methods.add(
|
||||
ImmutableMethod(
|
||||
definingClass,
|
||||
"patch_getNavigationIcon",
|
||||
listOf(),
|
||||
returnType,
|
||||
AccessFlags.PUBLIC.value or AccessFlags.FINAL.value,
|
||||
null,
|
||||
null,
|
||||
MutableMethodImplementation(2),
|
||||
).toMutable().apply {
|
||||
addInstructions(
|
||||
0,
|
||||
"""
|
||||
invoke-virtual { p0 }, $definingClass->$obfuscatedMethodName()$returnType
|
||||
move-result-object v0
|
||||
return-object v0
|
||||
"""
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
hookNavigationButtonCreated = { extensionClassDescriptor ->
|
||||
navigationBarHookCallbackFingerprint.method.addInstruction(
|
||||
0,
|
||||
"invoke-static { p0, p1 }, " +
|
||||
"$extensionClassDescriptor->navigationTabCreated" +
|
||||
"invoke-static { p0, p1 }, $extensionClassDescriptor->navigationTabCreated" +
|
||||
"(${EXTENSION_NAVIGATION_BUTTON_DESCRIPTOR}Landroid/view/View;)V",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.patches.youtube.misc.playertype
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@@ -15,6 +16,12 @@ internal val playerTypeFingerprint = fingerprint {
|
||||
custom { _, classDef -> classDef.endsWith("/YouTubePlayerOverlaysLayout;") }
|
||||
}
|
||||
|
||||
internal val reelWatchPagerFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("Landroid/view/View;")
|
||||
literal { reelWatchPlayerId }
|
||||
}
|
||||
|
||||
internal val videoStateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
|
||||
@@ -4,15 +4,34 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PlayerTypeHookPatch;"
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/PlayerTypeHookPatch;"
|
||||
|
||||
internal var reelWatchPlayerId = -1L
|
||||
private set
|
||||
|
||||
private val playerTypeHookResourcePatch = resourcePatch {
|
||||
dependsOn(resourceMappingPatch)
|
||||
|
||||
execute {
|
||||
reelWatchPlayerId = resourceMappings["id", "reel_watch_player"]
|
||||
}
|
||||
}
|
||||
|
||||
val playerTypeHookPatch = bytecodePatch(
|
||||
description = "Hook to get the current player type and video playback state.",
|
||||
) {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
dependsOn(sharedExtensionPatch, playerTypeHookResourcePatch)
|
||||
|
||||
execute {
|
||||
playerTypeFingerprint.method.addInstruction(
|
||||
@@ -20,6 +39,17 @@ val playerTypeHookPatch = bytecodePatch(
|
||||
"invoke-static {p1}, $EXTENSION_CLASS_DESCRIPTOR->setPlayerType(Ljava/lang/Enum;)V",
|
||||
)
|
||||
|
||||
reelWatchPagerFingerprint.method.apply {
|
||||
val literalIndex = indexOfFirstLiteralInstructionOrThrow(reelWatchPlayerId)
|
||||
val registerIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT_OBJECT)
|
||||
val viewRegister = getInstruction<OneRegisterInstruction>(registerIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
registerIndex + 1,
|
||||
"invoke-static { v$viewRegister }, $EXTENSION_CLASS_DESCRIPTOR->onShortsCreate(Landroid/view/View;)V"
|
||||
)
|
||||
}
|
||||
|
||||
videoStateFingerprint.method.apply {
|
||||
val endIndex = videoStateFingerprint.patternMatch!!.endIndex
|
||||
val videoStateFieldName = getInstruction<ReferenceInstruction>(endIndex).reference
|
||||
@@ -27,9 +57,9 @@ val playerTypeHookPatch = bytecodePatch(
|
||||
addInstructions(
|
||||
0,
|
||||
"""
|
||||
iget-object v0, p1, $videoStateFieldName # copy VideoState parameter field
|
||||
invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoState(Ljava/lang/Enum;)V
|
||||
""",
|
||||
iget-object v0, p1, $videoStateFieldName # copy VideoState parameter field
|
||||
invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoState(Ljava/lang/Enum;)V
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
|
||||
val removeTrackingQueryParameterPatch = bytecodePatch(
|
||||
name = "Remove tracking query parameter",
|
||||
description = "Adds an option to remove the tracking info from links you share.",
|
||||
description = "Adds an option to remove the tracking parameter from links you share.",
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
|
||||
@@ -301,11 +301,9 @@ object PreferenceScreen : BasePreferenceScreen() {
|
||||
summaryKey = null,
|
||||
)
|
||||
|
||||
// Don't sort, because title sorting scatters the custom color preferences.
|
||||
val SEEKBAR = Screen(
|
||||
key = "revanced_settings_screen_07_seekbar",
|
||||
summaryKey = null,
|
||||
sorting = Sorting.UNSORTED,
|
||||
)
|
||||
val SWIPE_CONTROLS = Screen(
|
||||
key = "revanced_settings_screen_08_swipe_controls",
|
||||
@@ -323,6 +321,7 @@ object PreferenceScreen : BasePreferenceScreen() {
|
||||
val VIDEO = Screen(
|
||||
key = "revanced_settings_screen_12_video",
|
||||
summaryKey = null,
|
||||
sorting = Sorting.BY_KEY,
|
||||
)
|
||||
|
||||
override fun commit(screen: PreferenceScreenPreference) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import app.revanced.patches.shared.misc.settings.preference.NonInteractivePrefer
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.spoof.spoofVideoStreamsPatch
|
||||
import app.revanced.patches.youtube.misc.playservice.is_19_34_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
|
||||
@@ -25,7 +27,10 @@ val spoofVideoStreamsPatch = spoofVideoStreamsPatch({
|
||||
dependsOn(
|
||||
userAgentClientSpoofPatch,
|
||||
settingsPatch,
|
||||
versionCheckPatch
|
||||
)
|
||||
}, {
|
||||
is_19_34_or_greater
|
||||
}, {
|
||||
addResources("youtube", "misc.fix.playback.spoofVideoStreamsPatch")
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package app.revanced.patches.youtube.video.quality
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook
|
||||
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
internal var videoQualityBottomSheetListFragmentTitle = -1L
|
||||
private set
|
||||
internal var videoQualityQuickMenuAdvancedMenuDescription = -1L
|
||||
private set
|
||||
|
||||
private val advancedVideoQualityMenuResourcePatch = resourcePatch {
|
||||
dependsOn(resourceMappingPatch)
|
||||
|
||||
execute {
|
||||
// Used for the old type of the video quality menu.
|
||||
videoQualityBottomSheetListFragmentTitle = resourceMappings[
|
||||
"layout",
|
||||
"video_quality_bottom_sheet_list_fragment_title",
|
||||
]
|
||||
|
||||
videoQualityQuickMenuAdvancedMenuDescription = resourceMappings[
|
||||
"string",
|
||||
"video_quality_quick_menu_advanced_menu_description",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/playback/quality/AdvancedVideoQualityMenuPatch;"
|
||||
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/components/AdvancedVideoQualityMenuFilter;"
|
||||
|
||||
internal val advancedVideoQualityMenuPatch = bytecodePatch {
|
||||
dependsOn(
|
||||
advancedVideoQualityMenuResourcePatch,
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
lithoFilterPatch,
|
||||
recyclerViewTreeHookPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("youtube", "video.quality.advancedVideoQualityMenuPatch")
|
||||
|
||||
settingsMenuVideoQualityGroup.add(
|
||||
SwitchPreference("revanced_advanced_video_quality_menu")
|
||||
)
|
||||
|
||||
// region Patch for the old type of the video quality menu.
|
||||
// Used for regular videos when spoofing to old app version,
|
||||
// and for the Shorts quality flyout on newer app versions.
|
||||
|
||||
videoQualityMenuViewInflateFingerprint.let {
|
||||
it.method.apply {
|
||||
val checkCastIndex = it.patternMatch!!.endIndex
|
||||
val listViewRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
checkCastIndex + 1,
|
||||
"invoke-static { v$listViewRegister }, $EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"showAdvancedVideoQualityMenu(Landroid/widget/ListView;)V",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Force YT to add the 'advanced' quality menu for Shorts.
|
||||
videoQualityMenuOptionsFingerprint.let {
|
||||
val patternMatch = it.patternMatch!!
|
||||
val startIndex = patternMatch.startIndex
|
||||
val insertIndex = patternMatch.endIndex
|
||||
if (startIndex != 0) throw PatchException("Unexpected opcode start index: $startIndex")
|
||||
|
||||
it.method.apply {
|
||||
val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
// A condition controls whether to show the three or four items quality menu.
|
||||
// Force the four items quality menu to make the "Advanced" item visible, necessary for the patch.
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->forceAdvancedVideoQualityMenuCreation(Z)Z
|
||||
move-result v$register
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Patch for the new type of the video quality menu.
|
||||
|
||||
addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR)
|
||||
|
||||
// Required to check if the video quality menu is currently shown in order to click on the "Advanced" item.
|
||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package app.revanced.patches.youtube.video.quality
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
@@ -35,3 +36,41 @@ internal val videoQualitySetterFingerprint = fingerprint {
|
||||
)
|
||||
strings("menu_item_video_quality")
|
||||
}
|
||||
|
||||
|
||||
internal val videoQualityMenuOptionsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.STATIC)
|
||||
returns("[L")
|
||||
parameters("Landroid/content/Context", "L", "L")
|
||||
opcodes(
|
||||
Opcode.CONST_4, // First instruction of method.
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.IGET_BOOLEAN, // Use the quality menu, that contains the advanced menu.
|
||||
Opcode.IF_NEZ,
|
||||
)
|
||||
literal { videoQualityQuickMenuAdvancedMenuDescription }
|
||||
}
|
||||
|
||||
internal val videoQualityMenuViewInflateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
parameters("L", "L", "L")
|
||||
opcodes(
|
||||
Opcode.INVOKE_SUPER,
|
||||
Opcode.CONST,
|
||||
Opcode.CONST_4,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_16,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CHECK_CAST,
|
||||
)
|
||||
literal { videoQualityBottomSheetListFragmentTitle }
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
|
||||
import app.revanced.patches.youtube.video.information.onCreateHook
|
||||
@@ -22,47 +22,47 @@ import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch;"
|
||||
|
||||
val rememberVideoQualityPatch = bytecodePatch(
|
||||
name = "Remember video quality",
|
||||
description = "Adds an option to remember the last video quality selected.",
|
||||
) {
|
||||
val rememberVideoQualityPatch = bytecodePatch {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
videoInformationPatch,
|
||||
playerTypeHookPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.16.39",
|
||||
"19.25.37",
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.45.38",
|
||||
"19.46.42",
|
||||
"19.47.53",
|
||||
),
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("youtube", "video.quality.rememberVideoQualityPatch")
|
||||
|
||||
PreferenceScreen.VIDEO.addPreferences(
|
||||
SwitchPreference("revanced_remember_video_quality_last_selected"),
|
||||
ListPreference(
|
||||
key = "revanced_video_quality_default_wifi",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_video_quality_default_entries",
|
||||
entryValuesKey = "revanced_video_quality_default_entry_values",
|
||||
),
|
||||
settingsMenuVideoQualityGroup.addAll(listOf(
|
||||
ListPreference(
|
||||
key = "revanced_video_quality_default_mobile",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_video_quality_default_entries",
|
||||
entryValuesKey = "revanced_video_quality_default_entry_values",
|
||||
),
|
||||
)
|
||||
ListPreference(
|
||||
key = "revanced_video_quality_default_wifi",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_video_quality_default_entries",
|
||||
entryValuesKey = "revanced_video_quality_default_entry_values",
|
||||
),
|
||||
SwitchPreference("revanced_remember_video_quality_last_selected"),
|
||||
|
||||
ListPreference(
|
||||
key = "revanced_shorts_quality_default_mobile",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_video_quality_default_entries",
|
||||
entryValuesKey = "revanced_video_quality_default_entry_values",
|
||||
),
|
||||
ListPreference(
|
||||
key = "revanced_shorts_quality_default_wifi",
|
||||
summaryKey = null,
|
||||
entriesKey = "revanced_video_quality_default_entries",
|
||||
entryValuesKey = "revanced_video_quality_default_entry_values",
|
||||
),
|
||||
SwitchPreference("revanced_remember_shorts_quality_last_selected")
|
||||
))
|
||||
|
||||
/*
|
||||
* The following code works by hooking the method which is called when the user selects a video quality
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package app.revanced.patches.youtube.video.quality
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
|
||||
/**
|
||||
* Video quality settings. Used to organize all speed related settings together.
|
||||
*/
|
||||
internal val settingsMenuVideoQualityGroup = mutableSetOf<BasePreference>()
|
||||
|
||||
@Suppress("unused")
|
||||
val videoQualityPatch = bytecodePatch(
|
||||
name = "Video quality",
|
||||
description = "Adds options to use the advanced video quality menu and set default video qualities."
|
||||
) {
|
||||
dependsOn(
|
||||
rememberVideoQualityPatch,
|
||||
advancedVideoQualityMenuPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.16.39",
|
||||
"19.25.37",
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.45.38",
|
||||
"19.46.42",
|
||||
"19.47.53",
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
PreferenceScreen.VIDEO.addPreferences(
|
||||
// Keep the preferences organized together.
|
||||
PreferenceCategory(
|
||||
key = "revanced_01_video_key", // Dummy key to force the quality preferences first.
|
||||
titleKey = null,
|
||||
sorting = Sorting.UNSORTED,
|
||||
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
|
||||
preferences = settingsMenuVideoQualityGroup
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,29 @@
|
||||
package app.revanced.patches.youtube.video.speed
|
||||
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.BasePreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
|
||||
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.video.speed.button.playbackSpeedButtonPatch
|
||||
import app.revanced.patches.youtube.video.speed.custom.customPlaybackSpeedPatch
|
||||
import app.revanced.patches.youtube.video.speed.remember.rememberPlaybackSpeedPatch
|
||||
|
||||
/**
|
||||
* Speed menu settings. Used to organize all speed related settings together.
|
||||
*/
|
||||
internal val settingsMenuVideoSpeedGroup = mutableSetOf<BasePreference>()
|
||||
|
||||
@Suppress("unused")
|
||||
val playbackSpeedPatch = bytecodePatch(
|
||||
name = "Playback speed",
|
||||
description = "Adds options to customize available playback speeds, remember the last playback speed selected " +
|
||||
description = "Adds options to customize available playback speeds, set default a playback speed, " +
|
||||
"and show a speed dialog button in the video player.",
|
||||
) {
|
||||
dependsOn(
|
||||
playbackSpeedButtonPatch,
|
||||
customPlaybackSpeedPatch,
|
||||
rememberPlaybackSpeedPatch,
|
||||
playbackSpeedButtonPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
@@ -26,6 +35,18 @@ val playbackSpeedPatch = bytecodePatch(
|
||||
"19.45.38",
|
||||
"19.46.42",
|
||||
"19.47.53",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
execute {
|
||||
PreferenceScreen.VIDEO.addPreferences(
|
||||
PreferenceCategory(
|
||||
key = "revanced_zz_video_key", // Dummy key to force the speed settings last.
|
||||
titleKey = null,
|
||||
sorting = Sorting.UNSORTED,
|
||||
tag = "app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory",
|
||||
preferences = settingsMenuVideoSpeedGroup
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.playercontrols.*
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
@@ -35,11 +36,12 @@ val playbackSpeedButtonPatch = bytecodePatch(
|
||||
description = "Adds the option to display playback speed dialog button in the video player.",
|
||||
) {
|
||||
dependsOn(
|
||||
playbackSpeedButtonResourcePatch,
|
||||
customPlaybackSpeedPatch,
|
||||
playerControlsPatch,
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
addResourcesPatch,
|
||||
customPlaybackSpeedPatch,
|
||||
playbackSpeedButtonResourcePatch,
|
||||
playerControlsPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
|
||||
@@ -25,8 +25,8 @@ import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
|
||||
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
|
||||
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook
|
||||
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
|
||||
import app.revanced.util.*
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
|
||||
@@ -60,24 +60,29 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
lithoFilterPatch,
|
||||
settingsPatch,
|
||||
recyclerViewTreeHookPatch,
|
||||
customPlaybackSpeedResourcePatch,
|
||||
addResourcesPatch,
|
||||
versionCheckPatch
|
||||
lithoFilterPatch,
|
||||
versionCheckPatch,
|
||||
recyclerViewTreeHookPatch,
|
||||
customPlaybackSpeedResourcePatch
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("youtube", "video.speed.custom.customPlaybackSpeedPatch")
|
||||
|
||||
PreferenceScreen.VIDEO.addPreferences(
|
||||
SwitchPreference("revanced_custom_speed_menu"),
|
||||
TextPreference("revanced_custom_playback_speeds", inputType = InputType.TEXT_MULTI_LINE),
|
||||
settingsMenuVideoSpeedGroup.addAll(
|
||||
listOf(
|
||||
SwitchPreference("revanced_custom_speed_menu"),
|
||||
TextPreference(
|
||||
"revanced_custom_playback_speeds",
|
||||
inputType = InputType.TEXT_MULTI_LINE
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
if (is_19_25_or_greater) {
|
||||
PreferenceScreen.VIDEO.addPreferences(
|
||||
settingsMenuVideoSpeedGroup.add(
|
||||
TextPreference("revanced_speed_tap_and_hold", inputType = InputType.NUMBER_DECIMAL),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.settings.preference.ListPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.video.information.*
|
||||
import app.revanced.patches.youtube.video.speed.custom.customPlaybackSpeedPatch
|
||||
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
@@ -22,26 +22,29 @@ internal val rememberPlaybackSpeedPatch = bytecodePatch {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
settingsPatch,
|
||||
videoInformationPatch,
|
||||
customPlaybackSpeedPatch,
|
||||
addResourcesPatch,
|
||||
videoInformationPatch,
|
||||
customPlaybackSpeedPatch
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("youtube", "video.speed.remember.rememberPlaybackSpeedPatch")
|
||||
|
||||
PreferenceScreen.VIDEO.addPreferences(
|
||||
SwitchPreference("revanced_remember_playback_speed_last_selected"),
|
||||
ListPreference(
|
||||
key = "revanced_playback_speed_default",
|
||||
summaryKey = null,
|
||||
// Entries and values are set by the extension code based on the actual speeds available.
|
||||
entriesKey = null,
|
||||
entryValuesKey = null,
|
||||
),
|
||||
settingsMenuVideoSpeedGroup.addAll(
|
||||
listOf(
|
||||
ListPreference(
|
||||
key = "revanced_playback_speed_default",
|
||||
summaryKey = null,
|
||||
// Entries and values are set by the extension code based on the actual speeds available.
|
||||
entriesKey = null,
|
||||
entryValuesKey = null,
|
||||
),
|
||||
SwitchPreference("revanced_remember_playback_speed_last_selected")
|
||||
)
|
||||
)
|
||||
|
||||
onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted")
|
||||
|
||||
userSelectedPlaybackSpeedHook(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
"userSelectedPlaybackSpeed",
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package app.revanced.patches.youtube.video.videoqualitymenu
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val videoQualityMenuOptionsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.STATIC)
|
||||
returns("[L")
|
||||
parameters("Landroid/content/Context", "L", "L")
|
||||
opcodes(
|
||||
Opcode.CONST_4, // First instruction of method.
|
||||
Opcode.CONST_4,
|
||||
Opcode.IF_EQZ,
|
||||
Opcode.IGET_BOOLEAN, // Use the quality menu, that contains the advanced menu.
|
||||
Opcode.IF_NEZ,
|
||||
)
|
||||
literal { videoQualityQuickMenuAdvancedMenuDescription }
|
||||
}
|
||||
|
||||
internal val videoQualityMenuViewInflateFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("L")
|
||||
parameters("L", "L", "L")
|
||||
opcodes(
|
||||
Opcode.INVOKE_SUPER,
|
||||
Opcode.CONST,
|
||||
Opcode.CONST_4,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CONST_16,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.CONST,
|
||||
Opcode.INVOKE_VIRTUAL,
|
||||
Opcode.MOVE_RESULT_OBJECT,
|
||||
Opcode.CHECK_CAST,
|
||||
)
|
||||
literal { videoQualityBottomSheetListFragmentTitle }
|
||||
}
|
||||
@@ -1,135 +1,10 @@
|
||||
package app.revanced.patches.youtube.video.videoqualitymenu
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
|
||||
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
|
||||
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook
|
||||
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
|
||||
internal var videoQualityBottomSheetListFragmentTitle = -1L
|
||||
private set
|
||||
internal var videoQualityQuickMenuAdvancedMenuDescription = -1L
|
||||
private set
|
||||
|
||||
private val restoreOldVideoQualityMenuResourcePatch = resourcePatch {
|
||||
dependsOn(
|
||||
settingsPatch,
|
||||
resourceMappingPatch,
|
||||
addResourcesPatch,
|
||||
)
|
||||
|
||||
execute {
|
||||
addResources("youtube", "video.videoqualitymenu.restoreOldVideoQualityMenuResourcePatch")
|
||||
|
||||
PreferenceScreen.VIDEO.addPreferences(
|
||||
SwitchPreference("revanced_restore_old_video_quality_menu"),
|
||||
)
|
||||
|
||||
// Used for the old type of the video quality menu.
|
||||
videoQualityBottomSheetListFragmentTitle = resourceMappings[
|
||||
"layout",
|
||||
"video_quality_bottom_sheet_list_fragment_title",
|
||||
]
|
||||
|
||||
videoQualityQuickMenuAdvancedMenuDescription = resourceMappings[
|
||||
"string",
|
||||
"video_quality_quick_menu_advanced_menu_description",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/components/VideoQualityMenuFilterPatch;"
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/playback/quality/RestoreOldVideoQualityMenuPatch;"
|
||||
import app.revanced.patches.youtube.video.quality.videoQualityPatch
|
||||
|
||||
@Suppress("unused")
|
||||
val restoreOldVideoQualityMenuPatch = bytecodePatch(
|
||||
name = "Restore old video quality menu",
|
||||
description = "Adds an option to restore the old video quality menu with specific video resolution options.",
|
||||
|
||||
) {
|
||||
dependsOn(
|
||||
sharedExtensionPatch,
|
||||
restoreOldVideoQualityMenuResourcePatch,
|
||||
lithoFilterPatch,
|
||||
recyclerViewTreeHookPatch,
|
||||
)
|
||||
|
||||
compatibleWith(
|
||||
"com.google.android.youtube"(
|
||||
"19.16.39",
|
||||
"19.25.37",
|
||||
"19.34.42",
|
||||
"19.43.41",
|
||||
"19.45.38",
|
||||
"19.46.42",
|
||||
"19.47.53",
|
||||
),
|
||||
)
|
||||
|
||||
execute {
|
||||
// region Patch for the old type of the video quality menu.
|
||||
// Used for regular videos when spoofing to old app version,
|
||||
// and for the Shorts quality flyout on newer app versions.
|
||||
|
||||
videoQualityMenuViewInflateFingerprint.method.apply {
|
||||
val checkCastIndex = videoQualityMenuViewInflateFingerprint.patternMatch!!.endIndex
|
||||
val listViewRegister = getInstruction<OneRegisterInstruction>(checkCastIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
checkCastIndex + 1,
|
||||
"invoke-static { v$listViewRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->" +
|
||||
"showOldVideoQualityMenu(Landroid/widget/ListView;)V",
|
||||
)
|
||||
}
|
||||
|
||||
// Force YT to add the 'advanced' quality menu for Shorts.
|
||||
val patternMatch = videoQualityMenuOptionsFingerprint.patternMatch!!
|
||||
val startIndex = patternMatch.startIndex
|
||||
if (startIndex != 0) throw PatchException("Unexpected opcode start index: $startIndex")
|
||||
val insertIndex = patternMatch.endIndex
|
||||
|
||||
videoQualityMenuOptionsFingerprint.method.apply {
|
||||
val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
|
||||
|
||||
// A condition controls whether to show the three or four items quality menu.
|
||||
// Force the four items quality menu to make the "Advanced" item visible, necessary for the patch.
|
||||
addInstructions(
|
||||
insertIndex,
|
||||
"""
|
||||
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->forceAdvancedVideoQualityMenuCreation(Z)Z
|
||||
move-result v$register
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Patch for the new type of the video quality menu.
|
||||
|
||||
addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR)
|
||||
|
||||
// Required to check if the video quality menu is currently shown in order to click on the "Advanced" item.
|
||||
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
|
||||
|
||||
// endregion
|
||||
}
|
||||
}
|
||||
@Deprecated("Use 'Video Quality' instead.")
|
||||
val restoreOldVideoQualityMenuPatch = bytecodePatch {
|
||||
dependsOn(videoQualityPatch)
|
||||
}
|
||||
@@ -178,8 +178,7 @@ fun Method.indexOfFirstLiteralInstructionReversedOrThrow(literal: Long): Int {
|
||||
*
|
||||
* @return if the method contains a literal with the given value.
|
||||
*/
|
||||
fun Method.containsLiteralInstruction(literal: Long) =
|
||||
indexOfFirstLiteralInstruction(literal) >= 0
|
||||
fun Method.containsLiteralInstruction(literal: Long) = indexOfFirstLiteralInstruction(literal) >= 0
|
||||
|
||||
/**
|
||||
* Traverse the class hierarchy starting from the given root class.
|
||||
@@ -205,25 +204,22 @@ fun BytecodePatchContext.traverseClassHierarchy(targetClass: MutableClass, callb
|
||||
* if the [Instruction] is not a [ReferenceInstruction] or the [Reference] is not of type [T].
|
||||
* @see ReferenceInstruction
|
||||
*/
|
||||
inline fun <reified T : Reference> Instruction.getReference() =
|
||||
(this as? ReferenceInstruction)?.reference as? T
|
||||
inline fun <reified T : Reference> Instruction.getReference() = (this as? ReferenceInstruction)?.reference as? T
|
||||
|
||||
/**
|
||||
* @return The index of the first opcode specified, or -1 if not found.
|
||||
* @see indexOfFirstInstructionOrThrow
|
||||
*/
|
||||
fun Method.indexOfFirstInstruction(targetOpcode: Opcode): Int =
|
||||
indexOfFirstInstruction(0, targetOpcode)
|
||||
fun Method.indexOfFirstInstruction(targetOpcode: Opcode): Int = indexOfFirstInstruction(0, targetOpcode)
|
||||
|
||||
/**
|
||||
* @param startIndex Optional starting index to start searching from.
|
||||
* @return The index of the first opcode specified, or -1 if not found.
|
||||
* @see indexOfFirstInstructionOrThrow
|
||||
*/
|
||||
fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int =
|
||||
indexOfFirstInstruction(startIndex) {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
fun Method.indexOfFirstInstruction(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstruction(startIndex) {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the first [Instruction] that matches the predicate, starting from [startIndex].
|
||||
@@ -251,23 +247,21 @@ fun Method.indexOfFirstInstruction(startIndex: Int = 0, filter: Instruction.() -
|
||||
* @throws PatchException
|
||||
* @see indexOfFirstInstruction
|
||||
*/
|
||||
fun Method.indexOfFirstInstructionOrThrow(targetOpcode: Opcode): Int =
|
||||
indexOfFirstInstructionOrThrow(0, targetOpcode)
|
||||
fun Method.indexOfFirstInstructionOrThrow(targetOpcode: Opcode): Int = indexOfFirstInstructionOrThrow(0, targetOpcode)
|
||||
|
||||
/**
|
||||
* @return The index of the first opcode specified, starting from the index specified.
|
||||
* @throws PatchException
|
||||
* @see indexOfFirstInstruction
|
||||
*/
|
||||
fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int =
|
||||
indexOfFirstInstructionOrThrow(startIndex) {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, targetOpcode: Opcode): Int = indexOfFirstInstructionOrThrow(startIndex) {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of the first [Instruction] that matches the predicate, starting from [startIndex].
|
||||
*
|
||||
* @return the index of the instruction
|
||||
* @return The index of the instruction.
|
||||
* @throws PatchException
|
||||
* @see indexOfFirstInstruction
|
||||
*/
|
||||
@@ -288,10 +282,9 @@ fun Method.indexOfFirstInstructionOrThrow(startIndex: Int = 0, filter: Instructi
|
||||
* @return -1 if the instruction is not found.
|
||||
* @see indexOfFirstInstructionReversedOrThrow
|
||||
*/
|
||||
fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int =
|
||||
indexOfFirstInstructionReversed(startIndex) {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversed(startIndex) {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of matching instruction,
|
||||
@@ -316,23 +309,21 @@ fun Method.indexOfFirstInstructionReversed(startIndex: Int? = null, filter: Inst
|
||||
*
|
||||
* @return -1 if the instruction is not found.
|
||||
*/
|
||||
fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int =
|
||||
indexOfFirstInstructionReversed {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
fun Method.indexOfFirstInstructionReversed(targetOpcode: Opcode): Int = indexOfFirstInstructionReversed {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of matching instruction,
|
||||
* starting from and [startIndex] and searching down.
|
||||
*
|
||||
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
||||
* @return -1 if the instruction is not found.
|
||||
* @return The index of the instruction.
|
||||
* @see indexOfFirstInstructionReversed
|
||||
*/
|
||||
fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int =
|
||||
indexOfFirstInstructionReversedOrThrow(startIndex) {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targetOpcode: Opcode): Int = indexOfFirstInstructionReversedOrThrow(startIndex) {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of matching instruction,
|
||||
@@ -340,16 +331,16 @@ fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, targe
|
||||
*
|
||||
* @return -1 if the instruction is not found.
|
||||
*/
|
||||
fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int =
|
||||
indexOfFirstInstructionReversedOrThrow {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
fun Method.indexOfFirstInstructionReversedOrThrow(targetOpcode: Opcode): Int = indexOfFirstInstructionReversedOrThrow {
|
||||
opcode == targetOpcode
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the index of matching instruction,
|
||||
* starting from and [startIndex] and searching down.
|
||||
*
|
||||
* @param startIndex Optional starting index to search down from. Searching includes the start index.
|
||||
* @return -1 if the instruction is not found.
|
||||
* @return The index of the instruction.
|
||||
* @see indexOfFirstInstructionReversed
|
||||
*/
|
||||
fun Method.indexOfFirstInstructionReversedOrThrow(startIndex: Int? = null, filter: Instruction.() -> Boolean): Int {
|
||||
@@ -389,8 +380,7 @@ fun Method.findInstructionIndicesReversedOrThrow(filter: Instruction.() -> Boole
|
||||
* _Returns an empty list if no indices are found_
|
||||
* @see findInstructionIndicesReversedOrThrow
|
||||
*/
|
||||
fun Method.findInstructionIndicesReversed(opcode: Opcode): List<Int> =
|
||||
findInstructionIndicesReversed { this.opcode == opcode }
|
||||
fun Method.findInstructionIndicesReversed(opcode: Opcode): List<Int> = findInstructionIndicesReversed { this.opcode == opcode }
|
||||
|
||||
/**
|
||||
* @return An immutable list of indices of the opcode in reverse order.
|
||||
@@ -408,12 +398,18 @@ internal fun MutableMethod.insertFeatureFlagBooleanOverride(literal: Long, exten
|
||||
val index = indexOfFirstInstructionOrThrow(literalIndex, Opcode.MOVE_RESULT)
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
|
||||
val operation = if (register < 16) {
|
||||
"invoke-static { v$register }"
|
||||
} else {
|
||||
"invoke-static/range { v$register .. v$register }"
|
||||
}
|
||||
|
||||
addInstructions(
|
||||
index + 1,
|
||||
"""
|
||||
invoke-static { v$register }, $extensionsMethod
|
||||
$operation, $extensionsMethod
|
||||
move-result v$register
|
||||
"""
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -458,7 +454,7 @@ fun MutableMethod.returnEarly(bool: Boolean = false) {
|
||||
return v0
|
||||
"""
|
||||
|
||||
else -> throw Exception("This case should never happen.")
|
||||
else -> throw Exception("Return type is not supported: $this")
|
||||
}
|
||||
|
||||
addInstructions(0, stringInstructions)
|
||||
|
||||
@@ -50,9 +50,9 @@ Second \"item\" text"</string>
|
||||
This button usually appears when searching for a YT creator. -->
|
||||
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
|
||||
<!-- 'Component path builder strings' is the technical name for identifying the Litho UI layout items to hide. This is an advanced feature and most users will never use this. -->
|
||||
<!-- For localization it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
|
||||
<!-- For localization, it is preferred, but not required, if 'LeBlanc' is replaced with a localized name or a familiar word that has upper case letters in the middle of the word.
|
||||
This is because keywords can be in any language, and showing an example in the localized script helps convey this. -->
|
||||
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
|
||||
<!-- Translations _must_ use a localized example. For languages that do not use spaces between words (Chinese, Japanese, etc.) the English AI example should be used since no localized examples exist. Or if using machine translations, or if nobody wants to think of a localized example, then the English 'ai' example should be left as-is. -->
|
||||
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
|
||||
</patch>
|
||||
<patch id="ad.general.hideAdsResourcePatch">
|
||||
@@ -156,6 +156,7 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.sponsorblock.sponsorBlockResourcePatch">
|
||||
<!-- Translations should use language similar to 'revanced_ryd_compact_layout_title' -->
|
||||
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
|
||||
<!-- A segment start and end time, such as "02:10 to 03:40" -->
|
||||
<!-- Shown in the settings preferences, and translations can be any text length. -->
|
||||
</patch>
|
||||
<patch id="layout.formfactor.changeFormFactorPatch">
|
||||
@@ -163,7 +164,6 @@ Second \"item\" text"</string>
|
||||
<patch id="layout.spoofappversion.spoofAppVersionPatch">
|
||||
<!-- It is ideal, but not required, if the text here appears is alphabetically after the text used for 'revanced_spoof_app_version_title'.
|
||||
This is because the 'General layout' menu uses alphabetic sorting, and it functionally works better if the spoof target selector appears below the 'Spoof app version' UI switch -->
|
||||
<!-- 'RYD' is 'Return YouTube Dislike' -->
|
||||
</patch>
|
||||
<patch id="layout.startpage.changeStartPagePatch">
|
||||
</patch>
|
||||
@@ -177,8 +177,6 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="layout.theme.themePatch">
|
||||
</patch>
|
||||
<patch id="layout.theme.themeResourcePatch">
|
||||
</patch>
|
||||
<patch id="layout.thumbnails.bypassImageRegionRestrictionsPatch">
|
||||
</patch>
|
||||
<patch id="layout.thumbnails.alternativeThumbnailsPatch">
|
||||
@@ -219,7 +217,7 @@ Second \"item\" text"</string>
|
||||
</patch>
|
||||
<patch id="video.hdr.disableHdrPatch">
|
||||
</patch>
|
||||
<patch id="video.videoqualitymenu.restoreOldVideoQualityMenuResourcePatch">
|
||||
<patch id="video.quality.advancedVideoQualityMenuPatch">
|
||||
</patch>
|
||||
<patch id="interaction.seekbar.enableSlideToSeekPatch">
|
||||
</patch>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user