mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-12-07 18:03:55 +01:00
Compare commits
309 Commits
v5.3.0-dev
...
v5.13.0-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09773e8934 | ||
|
|
d77d5bfbdd | ||
|
|
a84bded9e7 | ||
|
|
e664a24f73 | ||
|
|
5bf964fff6 | ||
|
|
0c0bbb8713 | ||
|
|
8afe48cd92 | ||
|
|
dde8ea31cb | ||
|
|
d3abbe3e93 | ||
|
|
c8179776ed | ||
|
|
c6c6516b12 | ||
|
|
d6eae01e12 | ||
|
|
ba88603f4b | ||
|
|
d5aab3d464 | ||
|
|
fca2f70c0e | ||
|
|
348f7e12cb | ||
|
|
b6b7208eeb | ||
|
|
a2c79f1349 | ||
|
|
4f5bb3c915 | ||
|
|
4b77d27c77 | ||
|
|
7991c80129 | ||
|
|
6baf4ea2ac | ||
|
|
c89538c8f5 | ||
|
|
94fb367618 | ||
|
|
354835966d | ||
|
|
168f9b769e | ||
|
|
e4c4b3a73a | ||
|
|
fce98b4960 | ||
|
|
839aa81e9c | ||
|
|
905bb0ea5f | ||
|
|
a94a663859 | ||
|
|
04b37dd55a | ||
|
|
2382e9d09e | ||
|
|
97f504976a | ||
|
|
0a6c5158e0 | ||
|
|
a959d798e8 | ||
|
|
39a0b9bda6 | ||
|
|
92c38b2cb4 | ||
|
|
4732210d4b | ||
|
|
f30a49f1cb | ||
|
|
bcd157dd2b | ||
|
|
d299ea5973 | ||
|
|
a20021e290 | ||
|
|
373ca966f3 | ||
|
|
12de922afa | ||
|
|
580bb3cf6c | ||
|
|
421af92f4c | ||
|
|
4d03e1b5a1 | ||
|
|
24d68df6cd | ||
|
|
e9aee17746 | ||
|
|
7c4285e3e6 | ||
|
|
e3110271a7 | ||
|
|
0079eceb87 | ||
|
|
af2a97cb16 | ||
|
|
aeb552e8f2 | ||
|
|
6e936fea42 | ||
|
|
f63769f39f | ||
|
|
1c9ab20a63 | ||
|
|
cdeccad908 | ||
|
|
399889c6fa | ||
|
|
ec77861410 | ||
|
|
b5afc6d827 | ||
|
|
b7ebfddf65 | ||
|
|
2742aca48b | ||
|
|
14ca4d3288 | ||
|
|
a06c0318bf | ||
|
|
7f9f668435 | ||
|
|
76fd33ca54 | ||
|
|
9a653e9c5a | ||
|
|
f81b658fb7 | ||
|
|
7ff39d89d6 | ||
|
|
78ab0ec2bd | ||
|
|
3ab67f1539 | ||
|
|
8652cd613f | ||
|
|
bc8388713c | ||
|
|
d4b2e3be3e | ||
|
|
57c48b7829 | ||
|
|
aaa7523ee4 | ||
|
|
785df4fe69 | ||
|
|
83208eb50d | ||
|
|
9437db11eb | ||
|
|
1843c8bf70 | ||
|
|
778b51fbff | ||
|
|
ee0fdcdf86 | ||
|
|
57cc73d9c4 | ||
|
|
043ebbb6d4 | ||
|
|
d5551923fc | ||
|
|
f844a1cd76 | ||
|
|
a7e3277cc1 | ||
|
|
6fa2deea69 | ||
|
|
dcca2a3697 | ||
|
|
018160fd9c | ||
|
|
680252967e | ||
|
|
e79eba81d9 | ||
|
|
a73db03671 | ||
|
|
055ad04281 | ||
|
|
aaeee4a895 | ||
|
|
654b339f66 | ||
|
|
64cdce28a6 | ||
|
|
d01b9a67c5 | ||
|
|
a72404eeab | ||
|
|
3ff104528e | ||
|
|
76bbd7ed2f | ||
|
|
2fdf0f85c1 | ||
|
|
1d12c4156d | ||
|
|
c43050dce8 | ||
|
|
8104bbd7d7 | ||
|
|
8487888e6b | ||
|
|
6721a284cd | ||
|
|
6cde702854 | ||
|
|
7c8efcaf41 | ||
|
|
350ee02e3b | ||
|
|
df2d070a43 | ||
|
|
8167aaccc8 | ||
|
|
f4989ed0a5 | ||
|
|
8f5a0531bc | ||
|
|
622554de14 | ||
|
|
66e330ffe6 | ||
|
|
2afcd3d63d | ||
|
|
80d7c78cf6 | ||
|
|
d85bcc3c16 | ||
|
|
21368ea696 | ||
|
|
e687d3ed37 | ||
|
|
064b859d39 | ||
|
|
89882ddaf8 | ||
|
|
41881ba161 | ||
|
|
0615990138 | ||
|
|
70532313db | ||
|
|
e5e897de77 | ||
|
|
1e57ce9658 | ||
|
|
fcad0ab5bb | ||
|
|
91471eccf9 | ||
|
|
d559f016c6 | ||
|
|
5a82d26f03 | ||
|
|
e2eae499d9 | ||
|
|
64919d6443 | ||
|
|
c6ffaf86ae | ||
|
|
3ee99b7bf1 | ||
|
|
6f9bf4873f | ||
|
|
29a73089a3 | ||
|
|
74ef1841eb | ||
|
|
0c544d28e3 | ||
|
|
b1e5b99b44 | ||
|
|
7b90baadb5 | ||
|
|
4a6f3c8555 | ||
|
|
e7c6943ca7 | ||
|
|
ae1b987c0d | ||
|
|
9496438da1 | ||
|
|
fa51631ea6 | ||
|
|
8bf7108001 | ||
|
|
030eece04a | ||
|
|
30009b723d | ||
|
|
53b25ea7e9 | ||
|
|
189e1c90c4 | ||
|
|
f01603b3f3 | ||
|
|
3db5651e5c | ||
|
|
f3c4d6fd64 | ||
|
|
29dbc9ffbf | ||
|
|
fa4aa54f0c | ||
|
|
1d89ada07f | ||
|
|
8c529abad5 | ||
|
|
4ade7c7329 | ||
|
|
f35247a872 | ||
|
|
4de768febf | ||
|
|
1a5c86db93 | ||
|
|
dbba795468 | ||
|
|
0a9320551d | ||
|
|
9fac1614e7 | ||
|
|
2de3523c59 | ||
|
|
ad1e40b130 | ||
|
|
094a6aa6de | ||
|
|
a14e03e4bb | ||
|
|
6f40b6d30f | ||
|
|
1711e1c39d | ||
|
|
25372828d1 | ||
|
|
f58245c6cd | ||
|
|
87e1c7f4c8 | ||
|
|
55d01c92d1 | ||
|
|
ca21a69550 | ||
|
|
634d0b4058 | ||
|
|
47ea8d5ec8 | ||
|
|
9509ed53f3 | ||
|
|
39542ddf55 | ||
|
|
e1741130af | ||
|
|
e54eb3ce87 | ||
|
|
0ae756b0fc | ||
|
|
77a0ac5c9c | ||
|
|
899121b9de | ||
|
|
838edb48e7 | ||
|
|
b2665c916a | ||
|
|
4b81f7009b | ||
|
|
1a4c39a2ee | ||
|
|
99334d1e53 | ||
|
|
2850a6ed4e | ||
|
|
f28eb5105b | ||
|
|
69bed4d9fa | ||
|
|
a5f1efac27 | ||
|
|
b51be82cff | ||
|
|
b8635d0b88 | ||
|
|
78699c8bbf | ||
|
|
aeedec7fed | ||
|
|
32b614696b | ||
|
|
a0b63dfa23 | ||
|
|
f0f53cf72f | ||
|
|
cdb68209d1 | ||
|
|
7369f7b8d5 | ||
|
|
db521b940b | ||
|
|
25d7cc68ae | ||
|
|
9495064e6e | ||
|
|
64864c2cdb | ||
|
|
ad0ffb3328 | ||
|
|
06800324aa | ||
|
|
ec746cb05a | ||
|
|
67c5530ea6 | ||
|
|
cd08717783 | ||
|
|
7bac023ea5 | ||
|
|
1d0ec98bec | ||
|
|
3c603fac2d | ||
|
|
20a7ad4715 | ||
|
|
25a60e305e | ||
|
|
c7f42d9a3c | ||
|
|
670f100a29 | ||
|
|
19140e5918 | ||
|
|
1dde485013 | ||
|
|
5efcdd31c8 | ||
|
|
e6529837cb | ||
|
|
fe07033444 | ||
|
|
246333f3dc | ||
|
|
d82b02e4f5 | ||
|
|
44995a9f15 | ||
|
|
c87c788a26 | ||
|
|
4ef30618d1 | ||
|
|
b23e6c39fc | ||
|
|
de26766543 | ||
|
|
9168b5eaaf | ||
|
|
c43b9b3b03 | ||
|
|
5e8dfed3e8 | ||
|
|
d67dbba76f | ||
|
|
5dc93156e0 | ||
|
|
5275413ab7 | ||
|
|
248c05b670 | ||
|
|
9e6669d962 | ||
|
|
9c81d01cc8 | ||
|
|
59654788fc | ||
|
|
4c44982cde | ||
|
|
a7aab9aeca | ||
|
|
7a8486f562 | ||
|
|
ccb6a7f161 | ||
|
|
c792edfb77 | ||
|
|
339cd6cc70 | ||
|
|
68304fd96a | ||
|
|
4033048c9b | ||
|
|
9525137800 | ||
|
|
0cf05fa2b0 | ||
|
|
a9bfaf44e2 | ||
|
|
7b08051371 | ||
|
|
b217ca9f9d | ||
|
|
9482092579 | ||
|
|
134c2e52bd | ||
|
|
c348b10a35 | ||
|
|
9a9ec7ef18 | ||
|
|
e746507339 | ||
|
|
862ca077db | ||
|
|
138d43b34b | ||
|
|
8d06a4a8ad | ||
|
|
d7ca7c1733 | ||
|
|
8e0b7db82a | ||
|
|
b9d7867cee | ||
|
|
11216cd942 | ||
|
|
b163e5f64d | ||
|
|
5c2bbd0671 | ||
|
|
2062660d60 | ||
|
|
2d9f08a08e | ||
|
|
78c51182f2 | ||
|
|
feac2ab439 | ||
|
|
32be03c28d | ||
|
|
6a345eee37 | ||
|
|
61be7731e3 | ||
|
|
8295356f88 | ||
|
|
3ec25778eb | ||
|
|
3faf0ac160 | ||
|
|
3ff559878b | ||
|
|
ed9c78da1e | ||
|
|
eefb59020e | ||
|
|
18f18849f3 | ||
|
|
b172c38284 | ||
|
|
5b15602896 | ||
|
|
89c45afcc6 | ||
|
|
3c47bfff1a | ||
|
|
6af8e1b625 | ||
|
|
c44a4af406 | ||
|
|
cb857b0fce | ||
|
|
e0322afbf0 | ||
|
|
5f02f583be | ||
|
|
6462fb8cba | ||
|
|
f9dcce927e | ||
|
|
69f9ab8345 | ||
|
|
dd400ac2a0 | ||
|
|
538ed6d876 | ||
|
|
5ff94dc34a | ||
|
|
b04a11a885 | ||
|
|
4983e021f9 | ||
|
|
bee917f4ed | ||
|
|
c94376bc4c | ||
|
|
87fe83aacf | ||
|
|
92d282e963 | ||
|
|
4a88f650c2 | ||
|
|
8b67716506 | ||
|
|
95d56b1529 |
8
.github/workflows/build_pull_request.yml
vendored
8
.github/workflows/build_pull_request.yml
vendored
@@ -28,4 +28,10 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew build --no-daemon
|
run: ./gradlew :patches:buildAndroid --no-daemon
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: revanced-patches
|
||||||
|
path: patches/build/libs
|
||||||
|
|||||||
24
.github/workflows/pull_strings.yml
vendored
24
.github/workflows/pull_strings.yml
vendored
@@ -1,6 +1,8 @@
|
|||||||
name: Pull strings
|
name: Pull strings
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 */6 * * *"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -14,23 +16,29 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
|
||||||
ref: dev
|
ref: dev
|
||||||
|
fetch-depth: 0
|
||||||
|
clean: true
|
||||||
|
|
||||||
- name: Pull strings
|
- name: Pull strings
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
with:
|
with:
|
||||||
config: crowdin.yml
|
config: crowdin.yml
|
||||||
|
upload_sources: false
|
||||||
download_translations: true
|
download_translations: true
|
||||||
|
skip_ref_checkout: true
|
||||||
localization_branch_name: feat/translations
|
localization_branch_name: feat/translations
|
||||||
create_pull_request: true
|
create_pull_request: false
|
||||||
pull_request_title: "chore: Sync translations"
|
|
||||||
pull_request_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)"
|
|
||||||
pull_request_base_branch_name: "dev"
|
|
||||||
commit_message: "chore: Sync translations"
|
|
||||||
github_user_name: revanced-bot
|
|
||||||
github_user_email: github@revanced.app
|
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||||
|
|
||||||
|
- name: Open pull request
|
||||||
|
if: github.event_name == 'workflow_dispatch'
|
||||||
|
uses: repo-sync/pull-request@v2
|
||||||
|
with:
|
||||||
|
source_branch: feat/translations
|
||||||
|
destination_branch: dev
|
||||||
|
pr_title: "chore: Sync translations"
|
||||||
|
pr_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)"
|
||||||
|
|||||||
5
.github/workflows/push_strings.yml
vendored
5
.github/workflows/push_strings.yml
vendored
@@ -18,6 +18,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Preprocess strings
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: ./gradlew clean preprocessCrowdinStrings
|
||||||
|
|
||||||
- name: Push strings
|
- name: Push strings
|
||||||
uses: crowdin/github-action@v2
|
uses: crowdin/github-action@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Build
|
- name: Build
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: ./gradlew build clean
|
run: ./gradlew :patches:buildAndroid clean
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|||||||
913
CHANGELOG.md
913
CHANGELOG.md
@@ -1,3 +1,916 @@
|
|||||||
|
# [5.13.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.9...v5.13.0-dev.10) (2025-02-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Copy video URL:** Use correct button ordering ([5e622cc](https://github.com/ReVanced/revanced-patches/commit/5e622ccf66d34af31c6026fa7f4d332460c6ecb0))
|
||||||
|
|
||||||
|
# [5.13.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.8...v5.13.0-dev.9) (2025-02-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **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))
|
||||||
|
|
||||||
|
# [5.13.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.7...v5.13.0-dev.8) (2025-02-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **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))
|
||||||
|
|
||||||
|
# [5.13.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.6...v5.13.0-dev.7) (2025-02-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **TikTok:** Resolve startup app crash ([6466398](https://github.com/ReVanced/revanced-patches/commit/64663983b84de1f28636205f61bf0a24c83968d1))
|
||||||
|
|
||||||
|
# [5.13.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.5...v5.13.0-dev.6) (2025-02-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **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))
|
||||||
|
|
||||||
|
# [5.13.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.4...v5.13.0-dev.5) (2025-02-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **TikTok:** Resolve startup app crash ([c14bc24](https://github.com/ReVanced/revanced-patches/commit/c14bc244550de30eca975ca7c09e8eb0c47534b5))
|
||||||
|
|
||||||
|
# [5.13.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.3...v5.13.0-dev.4) (2025-02-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **TikTok:** Resolve startup app crash ([d700076](https://github.com/ReVanced/revanced-patches/commit/d7000768a5e5a688c9f4e48858ac34e352222c1e))
|
||||||
|
|
||||||
|
# [5.13.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.2...v5.13.0-dev.3) (2025-02-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **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))
|
||||||
|
|
||||||
|
# [5.13.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.1...v5.13.0-dev.2) (2025-02-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **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))
|
||||||
|
|
||||||
|
# [5.13.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.12.0...v5.13.0-dev.1) (2025-02-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Do not hide 'Show anyway' button in search results ([4ac8854](https://github.com/ReVanced/revanced-patches/commit/4ac8854b99808a8957f3b0b7438e1e0cdedffbaf))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **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.12.0](https://github.com/ReVanced/revanced-patches/compare/v5.11.0...v5.12.0) (2025-02-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Allow changing default settings for existing app installs ([#4464](https://github.com/ReVanced/revanced-patches/issues/4464)) ([1bd7986](https://github.com/ReVanced/revanced-patches/commit/1bd7986823e774a929c8a9102a7cc96e245d5274))
|
||||||
|
* **Windy.app:** Remove obsolete `Unlock pro` patch ([#4428](https://github.com/ReVanced/revanced-patches/issues/4428)) ([83d116e](https://github.com/ReVanced/revanced-patches/commit/83d116e8fd3935ee431cfdf0b8e095d04ee77259))
|
||||||
|
* **YouTube - Spoof video streams:** Change default client to `Android TV` ([#4465](https://github.com/ReVanced/revanced-patches/issues/4465)) ([0412c79](https://github.com/ReVanced/revanced-patches/commit/0412c7901dc8599b6079d9c3ba26452f88af642b))
|
||||||
|
* **YouTube:** Remove obsolete 18.x targets ([#4454](https://github.com/ReVanced/revanced-patches/issues/4454)) ([a006758](https://github.com/ReVanced/revanced-patches/commit/a0067581d0f877e1b4eb1f888a25786f09676b2e))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Return YouTube Dislike:** add `Show estimated likes` setting ([#4443](https://github.com/ReVanced/revanced-patches/issues/4443)) ([9a88b42](https://github.com/ReVanced/revanced-patches/commit/9a88b4239fd63d5f91105fec8e7d59d318a5d09a))
|
||||||
|
* **YouTube - SponsorBlock:** Redesign skip buttons ([#4427](https://github.com/ReVanced/revanced-patches/issues/4427)) ([8f4883f](https://github.com/ReVanced/revanced-patches/commit/8f4883fc002420bfb4056401e23445c99e1d3fce))
|
||||||
|
* **YouTube Music:** Support version `8.05.50` ([#4439](https://github.com/ReVanced/revanced-patches/issues/4439)) ([b31fed9](https://github.com/ReVanced/revanced-patches/commit/b31fed98901fcda1bce6f05eb0de63280c689fa0))
|
||||||
|
* **YouTube Music:** Support version `8.05.51` ([128441e](https://github.com/ReVanced/revanced-patches/commit/128441e78bc0d096c3fc2f57782ab90c39c3ae4b))
|
||||||
|
|
||||||
|
# [5.12.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.6...v5.12.0-dev.7) (2025-02-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Change default client to `Android TV` ([#4465](https://github.com/ReVanced/revanced-patches/issues/4465)) ([0412c79](https://github.com/ReVanced/revanced-patches/commit/0412c7901dc8599b6079d9c3ba26452f88af642b))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube Music:** Support version `8.05.51` ([128441e](https://github.com/ReVanced/revanced-patches/commit/128441e78bc0d096c3fc2f57782ab90c39c3ae4b))
|
||||||
|
|
||||||
|
# [5.12.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.5...v5.12.0-dev.6) (2025-02-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Allow changing default settings for existing app installs ([#4464](https://github.com/ReVanced/revanced-patches/issues/4464)) ([1bd7986](https://github.com/ReVanced/revanced-patches/commit/1bd7986823e774a929c8a9102a7cc96e245d5274))
|
||||||
|
|
||||||
|
# [5.12.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.4...v5.12.0-dev.5) (2025-02-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Remove obsolete 18.x targets ([#4454](https://github.com/ReVanced/revanced-patches/issues/4454)) ([a006758](https://github.com/ReVanced/revanced-patches/commit/a0067581d0f877e1b4eb1f888a25786f09676b2e))
|
||||||
|
|
||||||
|
# [5.12.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.3...v5.12.0-dev.4) (2025-02-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube Music:** Support version `8.05.50` ([#4439](https://github.com/ReVanced/revanced-patches/issues/4439)) ([b31fed9](https://github.com/ReVanced/revanced-patches/commit/b31fed98901fcda1bce6f05eb0de63280c689fa0))
|
||||||
|
|
||||||
|
# [5.12.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.2...v5.12.0-dev.3) (2025-02-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Windy.app:** Remove obsolete `Unlock pro` patch ([#4428](https://github.com/ReVanced/revanced-patches/issues/4428)) ([83d116e](https://github.com/ReVanced/revanced-patches/commit/83d116e8fd3935ee431cfdf0b8e095d04ee77259))
|
||||||
|
|
||||||
|
# [5.12.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.1...v5.12.0-dev.2) (2025-02-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Return YouTube Dislike:** add `Show estimated likes` setting ([#4443](https://github.com/ReVanced/revanced-patches/issues/4443)) ([9a88b42](https://github.com/ReVanced/revanced-patches/commit/9a88b4239fd63d5f91105fec8e7d59d318a5d09a))
|
||||||
|
|
||||||
|
# [5.12.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.11.0...v5.12.0-dev.1) (2025-02-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Redesign skip buttons ([#4427](https://github.com/ReVanced/revanced-patches/issues/4427)) ([8f4883f](https://github.com/ReVanced/revanced-patches/commit/8f4883fc002420bfb4056401e23445c99e1d3fce))
|
||||||
|
|
||||||
|
# [5.11.0](https://github.com/ReVanced/revanced-patches/compare/v5.10.0...v5.11.0) (2025-02-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix broken `Remove screen capture restriction`, `Remove screenshot restriction`, `Spoof Wi-Fi connection`, and `Export internal data documents provider` patch ([#4405](https://github.com/ReVanced/revanced-patches/issues/4405)) ([1d52b74](https://github.com/ReVanced/revanced-patches/commit/1d52b7478d34e699d8c629eeaa9fdbb470b7d5c8))
|
||||||
|
* **YouTube - Enable slide to seek:** Change patch to default include ([50358cd](https://github.com/ReVanced/revanced-patches/commit/50358cddea3eef4051d248040d23f774521dce00))
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of community post ([#4404](https://github.com/ReVanced/revanced-patches/issues/4404)) ([f67ab2b](https://github.com/ReVanced/revanced-patches/commit/f67ab2baf25d543ceb55fcec48bda441ebf2b998))
|
||||||
|
* **YouTube - Theme:** Use custom seekbar color for cairo startup animation ([#4399](https://github.com/ReVanced/revanced-patches/issues/4399)) ([1cba294](https://github.com/ReVanced/revanced-patches/commit/1cba2948a6787118eb380ffcec35ee4fb99447ea))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Change start page:** Add additional start pages ([#4413](https://github.com/ReVanced/revanced-patches/issues/4413)) ([b434182](https://github.com/ReVanced/revanced-patches/commit/b434182df69313c4eb5f0dfd98101cb80e46ead2))
|
||||||
|
|
||||||
|
# [5.11.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.11.0-dev.1...v5.11.0-dev.2) (2025-02-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Fix broken `Remove screen capture restriction`, `Remove screenshot restriction`, `Spoof Wi-Fi connection`, and `Export internal data documents provider` patch ([#4405](https://github.com/ReVanced/revanced-patches/issues/4405)) ([1d52b74](https://github.com/ReVanced/revanced-patches/commit/1d52b7478d34e699d8c629eeaa9fdbb470b7d5c8))
|
||||||
|
|
||||||
|
# [5.11.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.3...v5.11.0-dev.1) (2025-02-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Change start page:** Add additional start pages ([#4413](https://github.com/ReVanced/revanced-patches/issues/4413)) ([b434182](https://github.com/ReVanced/revanced-patches/commit/b434182df69313c4eb5f0dfd98101cb80e46ead2))
|
||||||
|
|
||||||
|
## [5.10.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.2...v5.10.1-dev.3) (2025-02-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide new type of community post ([#4404](https://github.com/ReVanced/revanced-patches/issues/4404)) ([f67ab2b](https://github.com/ReVanced/revanced-patches/commit/f67ab2baf25d543ceb55fcec48bda441ebf2b998))
|
||||||
|
|
||||||
|
## [5.10.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.1...v5.10.1-dev.2) (2025-02-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Enable slide to seek:** Change patch to default include ([50358cd](https://github.com/ReVanced/revanced-patches/commit/50358cddea3eef4051d248040d23f774521dce00))
|
||||||
|
|
||||||
|
## [5.10.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.10.0...v5.10.1-dev.1) (2025-02-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Theme:** Use custom seekbar color for cairo startup animation ([#4399](https://github.com/ReVanced/revanced-patches/issues/4399)) ([1cba294](https://github.com/ReVanced/revanced-patches/commit/1cba2948a6787118eb380ffcec35ee4fb99447ea))
|
||||||
|
|
||||||
|
# [5.10.0](https://github.com/ReVanced/revanced-patches/compare/v5.9.0...v5.10.0) (2025-01-31)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **SwissId - Play integrity Removal:** Add recommended app version ([#4370](https://github.com/ReVanced/revanced-patches/issues/4370)) ([d8ed474](https://github.com/ReVanced/revanced-patches/commit/d8ed474b165f094fdedc32caaae1f82ebc99eb3d))
|
||||||
|
* Use correct path to fix invalid file paths ([5ff4ee8](https://github.com/ReVanced/revanced-patches/commit/5ff4ee823da55c7b135eab8b62e07be465612b55))
|
||||||
|
* **YouTube - Hide ads:** fix 'Hide the Visit store button on channel pages' not working ([#4364](https://github.com/ReVanced/revanced-patches/issues/4364)) ([9d63ea9](https://github.com/ReVanced/revanced-patches/commit/9d63ea9a10ab5128ce18a1f53a946e84550da258))
|
||||||
|
* **YouTube - Hide Ads:** Hide end screen store banner without leaving empty space ([#4367](https://github.com/ReVanced/revanced-patches/issues/4367)) ([7e68390](https://github.com/ReVanced/revanced-patches/commit/7e683906418434dd4e2104337d73a2292415c615))
|
||||||
|
* **YouTube - Hide ads:** Hide new types of tablet ads ([574bcc8](https://github.com/ReVanced/revanced-patches/commit/574bcc844746b7445ec3e93b47daceafefad85e7))
|
||||||
|
* **YouTube - Hide layout components:** Hide new kind of community post ([#4341](https://github.com/ReVanced/revanced-patches/issues/4341)) ([02685c4](https://github.com/ReVanced/revanced-patches/commit/02685c4567aca55f22d45dc238a7d1f0ea264143))
|
||||||
|
* **YouTube - Hide seekbar:** Do not hide player seekbar if hide feed seekbar is enabled ([#4333](https://github.com/ReVanced/revanced-patches/issues/4333)) ([f5cf6f2](https://github.com/ReVanced/revanced-patches/commit/f5cf6f2a445492d33815a9772f49deac2d70eba9))
|
||||||
|
* **YouTube - Hide video description components:** Use correct string key names ([0f28c2b](https://github.com/ReVanced/revanced-patches/commit/0f28c2b44c0051ea7ab3136433b84c73321cf5bd))
|
||||||
|
* **YouTube - Spoof video streams:** Update settings side effects summary text ([#4369](https://github.com/ReVanced/revanced-patches/issues/4369)) ([e5b3aa1](https://github.com/ReVanced/revanced-patches/commit/e5b3aa1cc6a2465cb006487d528de888bc7cd430))
|
||||||
|
* **YouTube - Theme:** Fix 19.25 - 19.45 patch error ([5b47a5f](https://github.com/ReVanced/revanced-patches/commit/5b47a5f0f6299daaae209341064fd85f16ca18a6))
|
||||||
|
* **YouTube - Theme:** Replace custom seekbar gradient colors instead of disabling ([#4329](https://github.com/ReVanced/revanced-patches/issues/4329)) ([f03da98](https://github.com/ReVanced/revanced-patches/commit/f03da983051021e0c372557a5354d5d967409564))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Add `Hide end screen store banner` ([#4351](https://github.com/ReVanced/revanced-patches/issues/4351)) ([5505087](https://github.com/ReVanced/revanced-patches/commit/55050878028fed82b0f583a9f7ba06b8f267f8ec))
|
||||||
|
* **YouTube - Hide video description components:** Add `Hide How this content was made section` ([#4355](https://github.com/ReVanced/revanced-patches/issues/4355)) ([68ec54e](https://github.com/ReVanced/revanced-patches/commit/68ec54ef850ae8d6461dd0ef2846e6efbb59e482))
|
||||||
|
* **YouTube - Theme:** Add option to use custom seekbar accent color ([#4337](https://github.com/ReVanced/revanced-patches/issues/4337)) ([952b4fc](https://github.com/ReVanced/revanced-patches/commit/952b4fc4c9291e1a3e71437b503857763c973dd4))
|
||||||
|
* **YouTube:** Add patch `Disable HDR video` ([#4347](https://github.com/ReVanced/revanced-patches/issues/4347)) ([0528f7c](https://github.com/ReVanced/revanced-patches/commit/0528f7cad856a2b1347e41944167b0583fc4a3d9))
|
||||||
|
|
||||||
|
# [5.10.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.10...v5.10.0-dev.11) (2025-01-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Use correct path to fix invalid file paths ([5ff4ee8](https://github.com/ReVanced/revanced-patches/commit/5ff4ee823da55c7b135eab8b62e07be465612b55))
|
||||||
|
|
||||||
|
# [5.10.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.9...v5.10.0-dev.10) (2025-01-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Hide new types of tablet ads ([574bcc8](https://github.com/ReVanced/revanced-patches/commit/574bcc844746b7445ec3e93b47daceafefad85e7))
|
||||||
|
|
||||||
|
# [5.10.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.8...v5.10.0-dev.9) (2025-01-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **SwissId - Play integrity Removal:** Add recommended app version ([#4370](https://github.com/ReVanced/revanced-patches/issues/4370)) ([d8ed474](https://github.com/ReVanced/revanced-patches/commit/d8ed474b165f094fdedc32caaae1f82ebc99eb3d))
|
||||||
|
|
||||||
|
# [5.10.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.7...v5.10.0-dev.8) (2025-01-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Update settings side effects summary text ([#4369](https://github.com/ReVanced/revanced-patches/issues/4369)) ([e5b3aa1](https://github.com/ReVanced/revanced-patches/commit/e5b3aa1cc6a2465cb006487d528de888bc7cd430))
|
||||||
|
|
||||||
|
# [5.10.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.6...v5.10.0-dev.7) (2025-01-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** fix 'Hide the Visit store button on channel pages' not working ([#4364](https://github.com/ReVanced/revanced-patches/issues/4364)) ([9d63ea9](https://github.com/ReVanced/revanced-patches/commit/9d63ea9a10ab5128ce18a1f53a946e84550da258))
|
||||||
|
|
||||||
|
# [5.10.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.5...v5.10.0-dev.6) (2025-01-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide Ads:** Hide end screen store banner without leaving empty space ([#4367](https://github.com/ReVanced/revanced-patches/issues/4367)) ([7e68390](https://github.com/ReVanced/revanced-patches/commit/7e683906418434dd4e2104337d73a2292415c615))
|
||||||
|
|
||||||
|
# [5.10.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.4...v5.10.0-dev.5) (2025-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide video description components:** Use correct string key names ([0f28c2b](https://github.com/ReVanced/revanced-patches/commit/0f28c2b44c0051ea7ab3136433b84c73321cf5bd))
|
||||||
|
|
||||||
|
# [5.10.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.3...v5.10.0-dev.4) (2025-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide video description components:** Add `Hide How this content was made section` ([#4355](https://github.com/ReVanced/revanced-patches/issues/4355)) ([68ec54e](https://github.com/ReVanced/revanced-patches/commit/68ec54ef850ae8d6461dd0ef2846e6efbb59e482))
|
||||||
|
|
||||||
|
# [5.10.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.2...v5.10.0-dev.3) (2025-01-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Add `Hide end screen store banner` ([#4351](https://github.com/ReVanced/revanced-patches/issues/4351)) ([5505087](https://github.com/ReVanced/revanced-patches/commit/55050878028fed82b0f583a9f7ba06b8f267f8ec))
|
||||||
|
|
||||||
|
# [5.10.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.1...v5.10.0-dev.2) (2025-01-25)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add patch `Disable HDR video` ([#4347](https://github.com/ReVanced/revanced-patches/issues/4347)) ([0528f7c](https://github.com/ReVanced/revanced-patches/commit/0528f7cad856a2b1347e41944167b0583fc4a3d9))
|
||||||
|
|
||||||
|
# [5.10.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.9.1-dev.4...v5.10.0-dev.1) (2025-01-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Theme:** Add option to use custom seekbar accent color ([#4337](https://github.com/ReVanced/revanced-patches/issues/4337)) ([952b4fc](https://github.com/ReVanced/revanced-patches/commit/952b4fc4c9291e1a3e71437b503857763c973dd4))
|
||||||
|
|
||||||
|
## [5.9.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.9.1-dev.3...v5.9.1-dev.4) (2025-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide new kind of community post ([#4341](https://github.com/ReVanced/revanced-patches/issues/4341)) ([02685c4](https://github.com/ReVanced/revanced-patches/commit/02685c4567aca55f22d45dc238a7d1f0ea264143))
|
||||||
|
|
||||||
|
## [5.9.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.9.1-dev.2...v5.9.1-dev.3) (2025-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide seekbar:** Do not hide player seekbar if hide feed seekbar is enabled ([#4333](https://github.com/ReVanced/revanced-patches/issues/4333)) ([f5cf6f2](https://github.com/ReVanced/revanced-patches/commit/f5cf6f2a445492d33815a9772f49deac2d70eba9))
|
||||||
|
|
||||||
|
## [5.9.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.9.1-dev.1...v5.9.1-dev.2) (2025-01-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Theme:** Fix 19.25 - 19.45 patch error ([5b47a5f](https://github.com/ReVanced/revanced-patches/commit/5b47a5f0f6299daaae209341064fd85f16ca18a6))
|
||||||
|
|
||||||
|
## [5.9.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.9.0...v5.9.1-dev.1) (2025-01-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Theme:** Replace custom seekbar gradient colors instead of disabling ([#4329](https://github.com/ReVanced/revanced-patches/issues/4329)) ([f03da98](https://github.com/ReVanced/revanced-patches/commit/f03da983051021e0c372557a5354d5d967409564))
|
||||||
|
|
||||||
|
# [5.9.0](https://github.com/ReVanced/revanced-patches/compare/v5.8.1...v5.9.0) (2025-01-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Resolve playback issues after changing from cellular to wifi ([#4277](https://github.com/ReVanced/revanced-patches/issues/4277)) ([e93e1c8](https://github.com/ReVanced/revanced-patches/commit/e93e1c8ec3367e941034e9c4e3725ec1db429a60))
|
||||||
|
* **YouTube - Spoof video streams:** Update client user-agent ([#4304](https://github.com/ReVanced/revanced-patches/issues/4304)) ([7917871](https://github.com/ReVanced/revanced-patches/commit/7917871f510b6b805370ef98a0cf8a4e2df0e900))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide feed components:** Handle new type of surveys ([#4295](https://github.com/ReVanced/revanced-patches/issues/4295)) ([c770e03](https://github.com/ReVanced/revanced-patches/commit/c770e03f3801367cb531af860fbdfa43dca89af0))
|
||||||
|
* **YouTube - Playback speed:** Add option to change 2x tap and hold speed ([#4307](https://github.com/ReVanced/revanced-patches/issues/4307)) ([02fb26e](https://github.com/ReVanced/revanced-patches/commit/02fb26e9458fb8635d497e6e78f964055244d738))
|
||||||
|
* **YouTube - Settings:** Add option to use new Cairo settings menus ([#4305](https://github.com/ReVanced/revanced-patches/issues/4305)) ([7b8a2a2](https://github.com/ReVanced/revanced-patches/commit/7b8a2a2721ab5351f8c0251401aceddf0c5327df))
|
||||||
|
|
||||||
|
# [5.9.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.9.0-dev.3...v5.9.0-dev.4) (2025-01-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Update client user-agent ([#4304](https://github.com/ReVanced/revanced-patches/issues/4304)) ([7917871](https://github.com/ReVanced/revanced-patches/commit/7917871f510b6b805370ef98a0cf8a4e2df0e900))
|
||||||
|
|
||||||
|
# [5.9.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.9.0-dev.2...v5.9.0-dev.3) (2025-01-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Settings:** Add option to use new Cairo settings menus ([#4305](https://github.com/ReVanced/revanced-patches/issues/4305)) ([7b8a2a2](https://github.com/ReVanced/revanced-patches/commit/7b8a2a2721ab5351f8c0251401aceddf0c5327df))
|
||||||
|
|
||||||
|
# [5.9.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.9.0-dev.1...v5.9.0-dev.2) (2025-01-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Playback speed:** Add option to change 2x tap and hold speed ([#4307](https://github.com/ReVanced/revanced-patches/issues/4307)) ([02fb26e](https://github.com/ReVanced/revanced-patches/commit/02fb26e9458fb8635d497e6e78f964055244d738))
|
||||||
|
|
||||||
|
# [5.9.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.8.2-dev.1...v5.9.0-dev.1) (2025-01-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide feed components:** Handle new type of surveys ([#4295](https://github.com/ReVanced/revanced-patches/issues/4295)) ([c770e03](https://github.com/ReVanced/revanced-patches/commit/c770e03f3801367cb531af860fbdfa43dca89af0))
|
||||||
|
|
||||||
|
## [5.8.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.8.1...v5.8.2-dev.1) (2025-01-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Resolve playback issues after changing from cellular to wifi ([#4277](https://github.com/ReVanced/revanced-patches/issues/4277)) ([e93e1c8](https://github.com/ReVanced/revanced-patches/commit/e93e1c8ec3367e941034e9c4e3725ec1db429a60))
|
||||||
|
|
||||||
|
## [5.8.1](https://github.com/ReVanced/revanced-patches/compare/v5.8.0...v5.8.1) (2025-01-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Add 'Android Creator' ([#4262](https://github.com/ReVanced/revanced-patches/issues/4262)) ([0479dd2](https://github.com/ReVanced/revanced-patches/commit/0479dd265e09b0accdf6ff6b00c8e938dc5b96c7))
|
||||||
|
|
||||||
|
## [5.8.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.8.0...v5.8.1-dev.1) (2025-01-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Add 'Android Creator' ([#4262](https://github.com/ReVanced/revanced-patches/issues/4262)) ([0479dd2](https://github.com/ReVanced/revanced-patches/commit/0479dd265e09b0accdf6ff6b00c8e938dc5b96c7))
|
||||||
|
|
||||||
|
# [5.8.0](https://github.com/ReVanced/revanced-patches/compare/v5.7.2...v5.8.0) (2024-12-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **GmsCore support:** Do not show battery optimization error on Android Automotive devices (Google built-in) ([#4218](https://github.com/ReVanced/revanced-patches/issues/4218)) ([d6e389c](https://github.com/ReVanced/revanced-patches/commit/d6e389cc43bc40724f032b230f70048276349a19))
|
||||||
|
* **YouTube - Exit fullscreen mode:** Exit fullscreen mode of first video opened after cold start ([be5cf2e](https://github.com/ReVanced/revanced-patches/commit/be5cf2e834d87d51b5d3061d46bd7154d6306787))
|
||||||
|
* **YouTube - Force original audio:** If stream spoofing to Android then show a summary text why force audio is not available ([#4220](https://github.com/ReVanced/revanced-patches/issues/4220)) ([029aee8](https://github.com/ReVanced/revanced-patches/commit/029aee8023f096413fc80a2c583b4fe55ecb10ac))
|
||||||
|
* **YouTube - Spoof video streams:** Ignore harmless error toast if hide ads is disabled ([c3423bb](https://github.com/ReVanced/revanced-patches/commit/c3423bb9e531cfa52f6d28e0b98bbe8ab8684c30))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Swipe controls:** Add option to enable/disable fullscreen swipe to next video ([#4222](https://github.com/ReVanced/revanced-patches/issues/4222)) ([119092f](https://github.com/ReVanced/revanced-patches/commit/119092fafa4129849246df15fe8076ed3b491b85))
|
||||||
|
* **YouTube - Hide Shorts components:** Add option to hide Shorts in watch history ([#4214](https://github.com/ReVanced/revanced-patches/issues/4214)) ([19c2742](https://github.com/ReVanced/revanced-patches/commit/19c2742aa367367c77bb50ddad6f8a20fef8ea0a))
|
||||||
|
* **YouTube - Spoof app version:** Add 'Restore old navigation and toolbar icons' ([f84e459](https://github.com/ReVanced/revanced-patches/commit/f84e459d3d54b3001586796ab4e114ebadf09043))
|
||||||
|
* **YouTube:** Add `Change form factor` patch ([#4217](https://github.com/ReVanced/revanced-patches/issues/4217)) ([644ac5b](https://github.com/ReVanced/revanced-patches/commit/644ac5baa68b209a32300149a2efa009b776f9a7))
|
||||||
|
* **YouTube:** Add `Exit fullscreen mode` patch ([#4223](https://github.com/ReVanced/revanced-patches/issues/4223)) ([bb5d03b](https://github.com/ReVanced/revanced-patches/commit/bb5d03bd89a3f932c77e4e9de90174c374933688))
|
||||||
|
* **YouTube:** Add in app option to select a preferred language for ReVanced specific text ([#4231](https://github.com/ReVanced/revanced-patches/issues/4231)) ([3932af3](https://github.com/ReVanced/revanced-patches/commit/3932af397ae89a0b30191cd870bd6cddb7a078db))
|
||||||
|
|
||||||
|
# [5.8.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.7...v5.8.0-dev.8) (2024-12-28)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add in app option to select a preferred language for ReVanced specific text ([#4231](https://github.com/ReVanced/revanced-patches/issues/4231)) ([3932af3](https://github.com/ReVanced/revanced-patches/commit/3932af397ae89a0b30191cd870bd6cddb7a078db))
|
||||||
|
|
||||||
|
# [5.8.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.6...v5.8.0-dev.7) (2024-12-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Ignore harmless error toast if hide ads is disabled ([c3423bb](https://github.com/ReVanced/revanced-patches/commit/c3423bb9e531cfa52f6d28e0b98bbe8ab8684c30))
|
||||||
|
|
||||||
|
# [5.8.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.5...v5.8.0-dev.6) (2024-12-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Exit fullscreen mode:** Exit fullscreen mode of first video opened after cold start ([be5cf2e](https://github.com/ReVanced/revanced-patches/commit/be5cf2e834d87d51b5d3061d46bd7154d6306787))
|
||||||
|
|
||||||
|
# [5.8.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.4...v5.8.0-dev.5) (2024-12-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add `Change form factor` patch ([#4217](https://github.com/ReVanced/revanced-patches/issues/4217)) ([644ac5b](https://github.com/ReVanced/revanced-patches/commit/644ac5baa68b209a32300149a2efa009b776f9a7))
|
||||||
|
|
||||||
|
# [5.8.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.3...v5.8.0-dev.4) (2024-12-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **GmsCore support:** Do not show battery optimization error on Android Automotive devices (Google built-in) ([#4218](https://github.com/ReVanced/revanced-patches/issues/4218)) ([d6e389c](https://github.com/ReVanced/revanced-patches/commit/d6e389cc43bc40724f032b230f70048276349a19))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Swipe controls:** Add option to enable/disable fullscreen swipe to next video ([#4222](https://github.com/ReVanced/revanced-patches/issues/4222)) ([119092f](https://github.com/ReVanced/revanced-patches/commit/119092fafa4129849246df15fe8076ed3b491b85))
|
||||||
|
* **YouTube:** Add `Exit fullscreen mode` patch ([#4223](https://github.com/ReVanced/revanced-patches/issues/4223)) ([bb5d03b](https://github.com/ReVanced/revanced-patches/commit/bb5d03bd89a3f932c77e4e9de90174c374933688))
|
||||||
|
|
||||||
|
# [5.8.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.2...v5.8.0-dev.3) (2024-12-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Force original audio:** If stream spoofing to Android then show a summary text why force audio is not available ([#4220](https://github.com/ReVanced/revanced-patches/issues/4220)) ([029aee8](https://github.com/ReVanced/revanced-patches/commit/029aee8023f096413fc80a2c583b4fe55ecb10ac))
|
||||||
|
|
||||||
|
# [5.8.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.8.0-dev.1...v5.8.0-dev.2) (2024-12-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Spoof app version:** Add 'Restore old navigation and toolbar icons' ([f84e459](https://github.com/ReVanced/revanced-patches/commit/f84e459d3d54b3001586796ab4e114ebadf09043))
|
||||||
|
|
||||||
|
# [5.8.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.2...v5.8.0-dev.1) (2024-12-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide Shorts components:** Add option to hide Shorts in watch history ([#4214](https://github.com/ReVanced/revanced-patches/issues/4214)) ([19c2742](https://github.com/ReVanced/revanced-patches/commit/19c2742aa367367c77bb50ddad6f8a20fef8ea0a))
|
||||||
|
|
||||||
|
## [5.7.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.1...v5.7.2) (2024-12-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Don't hide Shorts channel bar when toggling for video player ([9af6412](https://github.com/ReVanced/revanced-patches/commit/9af6412d92ec31e612eaabba6578453da0fc61d6))
|
||||||
|
* **YouTube - Spoof video streams:** Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds ([#4202](https://github.com/ReVanced/revanced-patches/issues/4202)) ([ab29f80](https://github.com/ReVanced/revanced-patches/commit/ab29f808a9f55b5ab0055533c1a6de549b0631a6))
|
||||||
|
|
||||||
|
## [5.7.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.2-dev.1...v5.7.2-dev.2) (2024-12-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Don't hide Shorts channel bar when toggling for video player ([9af6412](https://github.com/ReVanced/revanced-patches/commit/9af6412d92ec31e612eaabba6578453da0fc61d6))
|
||||||
|
|
||||||
|
## [5.7.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.1...v5.7.2-dev.1) (2024-12-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Add iOS TV client, restore iOS 'force AVC', show client type in stats for nerds ([#4202](https://github.com/ReVanced/revanced-patches/issues/4202)) ([ab29f80](https://github.com/ReVanced/revanced-patches/commit/ab29f808a9f55b5ab0055533c1a6de549b0631a6))
|
||||||
|
|
||||||
|
## [5.7.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.0...v5.7.1) (2024-12-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Show a toast and not a dialog if segment submitted successfully ([134b189](https://github.com/ReVanced/revanced-patches/commit/134b189791113dcf1a1cb7c87b8a0954f432730c))
|
||||||
|
* **YouTube - Spoof video streams:** Use 2 letter device language code ([33ff997](https://github.com/ReVanced/revanced-patches/commit/33ff9972000581aca92262f984efb114eeeb9537))
|
||||||
|
* **YouTube - Spoof video streams:** Use Android VR authentication if using default audio language ([#4191](https://github.com/ReVanced/revanced-patches/issues/4191)) ([98773cc](https://github.com/ReVanced/revanced-patches/commit/98773cc7d46e5c9c7715b82c8006f1ccbcc5443c))
|
||||||
|
* **YouTube - Theme:** Use dark theme color for status and navigation bar ([0240efe](https://github.com/ReVanced/revanced-patches/commit/0240efe33e5444625ca2b760c861c9046d3dc836))
|
||||||
|
* **YouTube:** Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) ([#4195](https://github.com/ReVanced/revanced-patches/issues/4195)) ([dda788c](https://github.com/ReVanced/revanced-patches/commit/dda788c58c789d4f91646ea8e8a8077f590ab6b3))
|
||||||
|
|
||||||
|
## [5.7.1-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.4...v5.7.1-dev.5) (2024-12-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Use 2 letter device language code ([33ff997](https://github.com/ReVanced/revanced-patches/commit/33ff9972000581aca92262f984efb114eeeb9537))
|
||||||
|
|
||||||
|
## [5.7.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.3...v5.7.1-dev.4) (2024-12-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Do not reset playback speed to 1.0x after closing comment thread (Fixes stock YouTube bug) ([#4195](https://github.com/ReVanced/revanced-patches/issues/4195)) ([dda788c](https://github.com/ReVanced/revanced-patches/commit/dda788c58c789d4f91646ea8e8a8077f590ab6b3))
|
||||||
|
|
||||||
|
## [5.7.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.2...v5.7.1-dev.3) (2024-12-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - SponsorBlock:** Show a toast and not a dialog if segment submitted successfully ([134b189](https://github.com/ReVanced/revanced-patches/commit/134b189791113dcf1a1cb7c87b8a0954f432730c))
|
||||||
|
|
||||||
|
## [5.7.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.7.1-dev.1...v5.7.1-dev.2) (2024-12-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Theme:** Use dark theme color for status and navigation bar ([0240efe](https://github.com/ReVanced/revanced-patches/commit/0240efe33e5444625ca2b760c861c9046d3dc836))
|
||||||
|
|
||||||
|
## [5.7.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.7.0...v5.7.1-dev.1) (2024-12-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Use Android VR authentication if using default audio language ([#4191](https://github.com/ReVanced/revanced-patches/issues/4191)) ([98773cc](https://github.com/ReVanced/revanced-patches/commit/98773cc7d46e5c9c7715b82c8006f1ccbcc5443c))
|
||||||
|
|
||||||
|
# [5.7.0](https://github.com/ReVanced/revanced-patches/compare/v5.6.0...v5.7.0) (2024-12-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Force original audio:** Use correct availability for settings UI ([a7eedcb](https://github.com/ReVanced/revanced-patches/commit/a7eedcb4cca6b7b12629c478c24c0899c80e3615))
|
||||||
|
* **YouTube - Spoof video stream:** Remove UI client type setting. Allow setting default audio language. ([#4184](https://github.com/ReVanced/revanced-patches/issues/4184)) ([99f3f29](https://github.com/ReVanced/revanced-patches/commit/99f3f29c649bf7693c05bbce2bb49bd53e05f050))
|
||||||
|
* **YouTube - Spoof video streams:** Remove iOS, add clients Android TV and Android Creator ([#4180](https://github.com/ReVanced/revanced-patches/issues/4180)) ([86abfb2](https://github.com/ReVanced/revanced-patches/commit/86abfb2b0d4675f0a1cb9ab244783075bfe89281))
|
||||||
|
* **YouTube:** Change fingerprints to support a wider range of target versions ([8a09174](https://github.com/ReVanced/revanced-patches/commit/8a09174def205a26ce49cb7815097e235069526a))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Support version `19.47.53` ([#4182](https://github.com/ReVanced/revanced-patches/issues/4182)) ([2089e61](https://github.com/ReVanced/revanced-patches/commit/2089e613d36c45352db7d852aaee0087b1c3e1a4))
|
||||||
|
|
||||||
|
# [5.7.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.4...v5.7.0-dev.1) (2024-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Support version `19.47.53` ([#4182](https://github.com/ReVanced/revanced-patches/issues/4182)) ([2089e61](https://github.com/ReVanced/revanced-patches/commit/2089e613d36c45352db7d852aaee0087b1c3e1a4))
|
||||||
|
|
||||||
|
## [5.6.1-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.3...v5.6.1-dev.4) (2024-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video stream:** Remove UI client type setting. Allow setting default audio language. ([#4184](https://github.com/ReVanced/revanced-patches/issues/4184)) ([99f3f29](https://github.com/ReVanced/revanced-patches/commit/99f3f29c649bf7693c05bbce2bb49bd53e05f050))
|
||||||
|
|
||||||
|
## [5.6.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.2...v5.6.1-dev.3) (2024-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Force original audio:** Use correct availability for settings UI ([a7eedcb](https://github.com/ReVanced/revanced-patches/commit/a7eedcb4cca6b7b12629c478c24c0899c80e3615))
|
||||||
|
|
||||||
|
## [5.6.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.6.1-dev.1...v5.6.1-dev.2) (2024-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Remove iOS, add clients Android TV and Android Creator ([#4180](https://github.com/ReVanced/revanced-patches/issues/4180)) ([86abfb2](https://github.com/ReVanced/revanced-patches/commit/86abfb2b0d4675f0a1cb9ab244783075bfe89281))
|
||||||
|
|
||||||
|
## [5.6.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.6.0...v5.6.1-dev.1) (2024-12-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Change fingerprints to support a wider range of target versions ([8a09174](https://github.com/ReVanced/revanced-patches/commit/8a09174def205a26ce49cb7815097e235069526a))
|
||||||
|
|
||||||
|
# [5.6.0](https://github.com/ReVanced/revanced-patches/compare/v5.5.1...v5.6.0) (2024-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Twitter - Change link sharing domain:** Use correct extension package ([ad7fab6](https://github.com/ReVanced/revanced-patches/commit/ad7fab67319ba23f267d27da9b74266965fc4be3))
|
||||||
|
* **YouTube - Force original audio:** Use correct original audio stream if app language is not English ([0d20171](https://github.com/ReVanced/revanced-patches/commit/0d2017133efac230887b5c2a331d87159df8af11))
|
||||||
|
* **YouTube - Hide layout components:** Hide new kind of community post ([#4155](https://github.com/ReVanced/revanced-patches/issues/4155)) ([08f68cb](https://github.com/ReVanced/revanced-patches/commit/08f68cb5d33f2cfe656d2f93d159c69981f31418))
|
||||||
|
* **YouTube - Miniplayer:** Use estimated maximum on screen size for devices with low density screens ([#4150](https://github.com/ReVanced/revanced-patches/issues/4150)) ([2694158](https://github.com/ReVanced/revanced-patches/commit/2694158c3c9935ede21c96832533222f850068df))
|
||||||
|
* **YouTube - Open Shorts in regular player:** Do not show the miniplayer after opening a Short while a video is playing ([894e366](https://github.com/ReVanced/revanced-patches/commit/894e36665d17d5a3a5728961d424dffc55faa50b))
|
||||||
|
* **YouTube - SponsorBlock:** Show create new segment error messages using a dialog ([#4148](https://github.com/ReVanced/revanced-patches/issues/4148)) ([5870906](https://github.com/ReVanced/revanced-patches/commit/587090636dfff0b358b15026cf7d47c65a4296dc))
|
||||||
|
* **YouTube - Spoof video streams:** Change default spoofing to iOS, allow setting a default language with Android VR ([#4171](https://github.com/ReVanced/revanced-patches/issues/4171)) ([171b4e7](https://github.com/ReVanced/revanced-patches/commit/171b4e7e40066e38fba773b7a6525e9a038779ef))
|
||||||
|
* **YouTube - Spoof video streams:** Update iOS client version ([df3aeed](https://github.com/ReVanced/revanced-patches/commit/df3aeed3b173e408fad80197a89ec5d003a2b328))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add `Open Shorts in regular player` patch ([#4153](https://github.com/ReVanced/revanced-patches/issues/4153)) ([c7c5e5b](https://github.com/ReVanced/revanced-patches/commit/c7c5e5b2b9cf63d8225bb6bd5e735ddf945b6c29))
|
||||||
|
|
||||||
|
# [5.6.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.5...v5.6.0-dev.6) (2024-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Update iOS client version ([df3aeed](https://github.com/ReVanced/revanced-patches/commit/df3aeed3b173e408fad80197a89ec5d003a2b328))
|
||||||
|
|
||||||
|
# [5.6.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.4...v5.6.0-dev.5) (2024-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Change default spoofing to iOS, allow setting a default language with Android VR ([#4171](https://github.com/ReVanced/revanced-patches/issues/4171)) ([171b4e7](https://github.com/ReVanced/revanced-patches/commit/171b4e7e40066e38fba773b7a6525e9a038779ef))
|
||||||
|
|
||||||
|
# [5.6.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.3...v5.6.0-dev.4) (2024-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Force original audio:** Use correct original audio stream if app language is not English ([0d20171](https://github.com/ReVanced/revanced-patches/commit/0d2017133efac230887b5c2a331d87159df8af11))
|
||||||
|
|
||||||
|
# [5.6.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.2...v5.6.0-dev.3) (2024-12-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Twitter - Change link sharing domain:** Use correct extension package ([ad7fab6](https://github.com/ReVanced/revanced-patches/commit/ad7fab67319ba23f267d27da9b74266965fc4be3))
|
||||||
|
|
||||||
|
# [5.6.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.6.0-dev.1...v5.6.0-dev.2) (2024-12-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Open Shorts in regular player:** Do not show the miniplayer after opening a Short while a video is playing ([894e366](https://github.com/ReVanced/revanced-patches/commit/894e36665d17d5a3a5728961d424dffc55faa50b))
|
||||||
|
|
||||||
|
# [5.6.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.5.2-dev.2...v5.6.0-dev.1) (2024-12-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add `Open Shorts in regular player` patch ([#4153](https://github.com/ReVanced/revanced-patches/issues/4153)) ([c7c5e5b](https://github.com/ReVanced/revanced-patches/commit/c7c5e5b2b9cf63d8225bb6bd5e735ddf945b6c29))
|
||||||
|
|
||||||
|
## [5.5.2-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.5.2-dev.1...v5.5.2-dev.2) (2024-12-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide layout components:** Hide new kind of community post ([#4155](https://github.com/ReVanced/revanced-patches/issues/4155)) ([08f68cb](https://github.com/ReVanced/revanced-patches/commit/08f68cb5d33f2cfe656d2f93d159c69981f31418))
|
||||||
|
|
||||||
|
## [5.5.2-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.5.1...v5.5.2-dev.1) (2024-12-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Miniplayer:** Use estimated maximum on screen size for devices with low density screens ([#4150](https://github.com/ReVanced/revanced-patches/issues/4150)) ([2694158](https://github.com/ReVanced/revanced-patches/commit/2694158c3c9935ede21c96832533222f850068df))
|
||||||
|
* **YouTube - SponsorBlock:** Show create new segment error messages using a dialog ([#4148](https://github.com/ReVanced/revanced-patches/issues/4148)) ([5870906](https://github.com/ReVanced/revanced-patches/commit/587090636dfff0b358b15026cf7d47c65a4296dc))
|
||||||
|
|
||||||
|
## [5.5.1](https://github.com/ReVanced/revanced-patches/compare/v5.5.0...v5.5.1) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Fix string translations ([52e04d3](https://github.com/ReVanced/revanced-patches/commit/52e04d340c1a85f3d683c67a15ae96529432d5fe))
|
||||||
|
|
||||||
|
## [5.5.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.5.0...v5.5.1-dev.1) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube:** Fix string translations ([52e04d3](https://github.com/ReVanced/revanced-patches/commit/52e04d340c1a85f3d683c67a15ae96529432d5fe))
|
||||||
|
|
||||||
|
# [5.5.0](https://github.com/ReVanced/revanced-patches/compare/v5.4.0...v5.5.0) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Twitch:** Change recommended target to the latest app version ([fb32972](https://github.com/ReVanced/revanced-patches/commit/fb32972f4de92dac1fc5d73f56a392a671c4e94b))
|
||||||
|
* **YouTube - Spoof video streams:** Make livestreams start at the current time when using iOS client ([#4137](https://github.com/ReVanced/revanced-patches/issues/4137)) ([140f484](https://github.com/ReVanced/revanced-patches/commit/140f484b4b251b0dfa94163a63f61f45f5302052))
|
||||||
|
* **YouTube Music:** Add `Spoof client patch` to fix playback ([#4132](https://github.com/ReVanced/revanced-patches/issues/4132)) ([b092508](https://github.com/ReVanced/revanced-patches/commit/b0925088e8b41636e285cb234593d545604ce461))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide feed components:** Remove obsolete `Hide search result shelf header` option ([#4134](https://github.com/ReVanced/revanced-patches/issues/4134)) ([c71443a](https://github.com/ReVanced/revanced-patches/commit/c71443a08883ab10ef2553213c03b00e7c580a43))
|
||||||
|
* **YouTube - Navigation buttons:** Add options to disable translucent status bar and navigation bar ([#4133](https://github.com/ReVanced/revanced-patches/issues/4133)) ([a2d2141](https://github.com/ReVanced/revanced-patches/commit/a2d2141cec9b0b4929e07a8010889b21c324b229))
|
||||||
|
* **YouTube:** Add `Force original audio` patch ([#4122](https://github.com/ReVanced/revanced-patches/issues/4122)) ([f4aa440](https://github.com/ReVanced/revanced-patches/commit/f4aa4406080b91f01d623e54b11b99ea849ddcdf))
|
||||||
|
|
||||||
|
# [5.5.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.4...v5.5.0-dev.5) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Navigation buttons:** Add options to disable translucent status bar and navigation bar ([#4133](https://github.com/ReVanced/revanced-patches/issues/4133)) ([a2d2141](https://github.com/ReVanced/revanced-patches/commit/a2d2141cec9b0b4929e07a8010889b21c324b229))
|
||||||
|
|
||||||
|
# [5.5.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.3...v5.5.0-dev.4) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Make livestreams start at the current time when using iOS client ([#4137](https://github.com/ReVanced/revanced-patches/issues/4137)) ([140f484](https://github.com/ReVanced/revanced-patches/commit/140f484b4b251b0dfa94163a63f61f45f5302052))
|
||||||
|
|
||||||
|
# [5.5.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.2...v5.5.0-dev.3) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Hide feed components:** Remove obsolete `Hide search result shelf header` option ([#4134](https://github.com/ReVanced/revanced-patches/issues/4134)) ([c71443a](https://github.com/ReVanced/revanced-patches/commit/c71443a08883ab10ef2553213c03b00e7c580a43))
|
||||||
|
|
||||||
|
# [5.5.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.5.0-dev.1...v5.5.0-dev.2) (2024-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube Music:** Add `Spoof client patch` to fix playback ([#4132](https://github.com/ReVanced/revanced-patches/issues/4132)) ([b092508](https://github.com/ReVanced/revanced-patches/commit/b0925088e8b41636e285cb234593d545604ce461))
|
||||||
|
|
||||||
|
# [5.5.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.4.1-dev.1...v5.5.0-dev.1) (2024-12-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add `Force original audio` patch ([#4122](https://github.com/ReVanced/revanced-patches/issues/4122)) ([f4aa440](https://github.com/ReVanced/revanced-patches/commit/f4aa4406080b91f01d623e54b11b99ea849ddcdf))
|
||||||
|
|
||||||
|
## [5.4.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.4.0...v5.4.1-dev.1) (2024-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Twitch:** Change recommended target to the latest app version ([fb32972](https://github.com/ReVanced/revanced-patches/commit/fb32972f4de92dac1fc5d73f56a392a671c4e94b))
|
||||||
|
|
||||||
|
# [5.4.0](https://github.com/ReVanced/revanced-patches/compare/v5.3.0...v5.4.0) (2024-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **GmsCore support:** Adjust presentation of battery optimization dialog ([#4091](https://github.com/ReVanced/revanced-patches/issues/4091)) ([5d8fc1b](https://github.com/ReVanced/revanced-patches/commit/5d8fc1bcd4e453298cfac086cdbdf279612bfb63))
|
||||||
|
* **TikTok - Settings:** Use correct colors for dark mode ([#4087](https://github.com/ReVanced/revanced-patches/issues/4087)) ([6bd22ff](https://github.com/ReVanced/revanced-patches/commit/6bd22ffa7e8af4d8f5d2d3b1711bd92c44b4e4aa))
|
||||||
|
* **TikTok - SIM Spoof:** Change patch to default off to fix login ([#4084](https://github.com/ReVanced/revanced-patches/issues/4084)) ([f4659a3](https://github.com/ReVanced/revanced-patches/commit/f4659a328eaf600e1e5f02a66fa2af4b6d8dc7c1))
|
||||||
|
* **YouTube - Hide ads:** Hide new type of featured promotions ([#4113](https://github.com/ReVanced/revanced-patches/issues/4113)) ([13c7592](https://github.com/ReVanced/revanced-patches/commit/13c7592b21defd27e3a7aa9b219ffc0247bb5914))
|
||||||
|
* **YouTube - Spoof video streams:** Fix error toast that is sometimes shown ([#4090](https://github.com/ReVanced/revanced-patches/issues/4090)) ([4c46cb2](https://github.com/ReVanced/revanced-patches/commit/4c46cb27a02c6f29626cd769b6a8e825645d5b16))
|
||||||
|
* **YouTube - Spoof video streams:** Resolve playback of age restricted videos ([#4096](https://github.com/ReVanced/revanced-patches/issues/4096)) ([839a404](https://github.com/ReVanced/revanced-patches/commit/839a4045f1bb1759d89047834e0b7695781e82a3))
|
||||||
|
* **YouTube Music - Bypass certificate checks:** Add a recommended target version ([#4104](https://github.com/ReVanced/revanced-patches/issues/4104)) ([17a5a6c](https://github.com/ReVanced/revanced-patches/commit/17a5a6c1691b0c23f601d3355b72f122c2bd5dcb))
|
||||||
|
* **YouTube Music - Spoof video streams:** Disable stable volume ([#4097](https://github.com/ReVanced/revanced-patches/issues/4097)) ([16bb9df](https://github.com/ReVanced/revanced-patches/commit/16bb9dfc299612f3922724c136878606987ab132))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add Internal data documents provider patch ([#3830](https://github.com/ReVanced/revanced-patches/issues/3830)) ([cb22f65](https://github.com/ReVanced/revanced-patches/commit/cb22f652ed678d81ffda9ece659b3971225d6931))
|
||||||
|
* **Change package name:** Add options to change provider and permission package names to handle installation conflicts ([75c740c](https://github.com/ReVanced/revanced-patches/commit/75c740c6ba2e0c62e567f7dc90cdad368fc4f372))
|
||||||
|
* **Twitch:** Make patches compatible with latest versions ([#4099](https://github.com/ReVanced/revanced-patches/issues/4099)) ([eecfbb7](https://github.com/ReVanced/revanced-patches/commit/eecfbb7122a9072e55e687f2c003f63108654888))
|
||||||
|
* **YouTube - Comments:** Add `Hide 'Chat summary'` ([#4110](https://github.com/ReVanced/revanced-patches/issues/4110)) ([269493c](https://github.com/ReVanced/revanced-patches/commit/269493cd198604f1438ea2850fb68fe900d0e56f))
|
||||||
|
|
||||||
|
# [5.4.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.10...v5.4.0-dev.11) (2024-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Twitch:** Make patches compatible with latest versions ([#4099](https://github.com/ReVanced/revanced-patches/issues/4099)) ([eecfbb7](https://github.com/ReVanced/revanced-patches/commit/eecfbb7122a9072e55e687f2c003f63108654888))
|
||||||
|
|
||||||
|
# [5.4.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.9...v5.4.0-dev.10) (2024-12-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Hide ads:** Hide new type of featured promotions ([#4113](https://github.com/ReVanced/revanced-patches/issues/4113)) ([13c7592](https://github.com/ReVanced/revanced-patches/commit/13c7592b21defd27e3a7aa9b219ffc0247bb5914))
|
||||||
|
|
||||||
|
# [5.4.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.8...v5.4.0-dev.9) (2024-12-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Comments:** Add `Hide 'Chat summary'` ([#4110](https://github.com/ReVanced/revanced-patches/issues/4110)) ([269493c](https://github.com/ReVanced/revanced-patches/commit/269493cd198604f1438ea2850fb68fe900d0e56f))
|
||||||
|
|
||||||
|
# [5.4.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.7...v5.4.0-dev.8) (2024-12-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube Music - Bypass certificate checks:** Add a recommended target version ([#4104](https://github.com/ReVanced/revanced-patches/issues/4104)) ([17a5a6c](https://github.com/ReVanced/revanced-patches/commit/17a5a6c1691b0c23f601d3355b72f122c2bd5dcb))
|
||||||
|
|
||||||
|
# [5.4.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.6...v5.4.0-dev.7) (2024-12-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **GmsCore support:** Adjust presentation of battery optimization dialog ([#4091](https://github.com/ReVanced/revanced-patches/issues/4091)) ([5d8fc1b](https://github.com/ReVanced/revanced-patches/commit/5d8fc1bcd4e453298cfac086cdbdf279612bfb63))
|
||||||
|
|
||||||
|
# [5.4.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.5...v5.4.0-dev.6) (2024-12-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube Music - Spoof video streams:** Disable stable volume ([#4097](https://github.com/ReVanced/revanced-patches/issues/4097)) ([16bb9df](https://github.com/ReVanced/revanced-patches/commit/16bb9dfc299612f3922724c136878606987ab132))
|
||||||
|
|
||||||
|
# [5.4.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.4...v5.4.0-dev.5) (2024-12-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Resolve playback of age restricted videos ([#4096](https://github.com/ReVanced/revanced-patches/issues/4096)) ([839a404](https://github.com/ReVanced/revanced-patches/commit/839a4045f1bb1759d89047834e0b7695781e82a3))
|
||||||
|
|
||||||
|
# [5.4.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.3...v5.4.0-dev.4) (2024-12-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Fix error toast that is sometimes shown ([#4090](https://github.com/ReVanced/revanced-patches/issues/4090)) ([4c46cb2](https://github.com/ReVanced/revanced-patches/commit/4c46cb27a02c6f29626cd769b6a8e825645d5b16))
|
||||||
|
|
||||||
|
# [5.4.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.2...v5.4.0-dev.3) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **TikTok - Settings:** Use correct colors for dark mode ([#4087](https://github.com/ReVanced/revanced-patches/issues/4087)) ([6bd22ff](https://github.com/ReVanced/revanced-patches/commit/6bd22ffa7e8af4d8f5d2d3b1711bd92c44b4e4aa))
|
||||||
|
|
||||||
|
# [5.4.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.4.0-dev.1...v5.4.0-dev.2) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **TikTok - SIM Spoof:** Change patch to default off to fix login ([#4084](https://github.com/ReVanced/revanced-patches/issues/4084)) ([f4659a3](https://github.com/ReVanced/revanced-patches/commit/f4659a328eaf600e1e5f02a66fa2af4b6d8dc7c1))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add Internal data documents provider patch ([#3830](https://github.com/ReVanced/revanced-patches/issues/3830)) ([cb22f65](https://github.com/ReVanced/revanced-patches/commit/cb22f652ed678d81ffda9ece659b3971225d6931))
|
||||||
|
|
||||||
|
# [5.4.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.3.0...v5.4.0-dev.1) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Change package name:** Add options to change provider and permission package names to handle installation conflicts ([75c740c](https://github.com/ReVanced/revanced-patches/commit/75c740c6ba2e0c62e567f7dc90cdad368fc4f372))
|
||||||
|
|
||||||
|
# [5.3.0](https://github.com/ReVanced/revanced-patches/compare/v5.2.3...v5.3.0) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Change package name:** Prevent applying the patch to known incompatible apps ([#3943](https://github.com/ReVanced/revanced-patches/issues/3943)) ([44936e7](https://github.com/ReVanced/revanced-patches/commit/44936e71e846f72f7279950232a5dba37765ceb3))
|
||||||
|
* **Reddit:** Fix patches by using correct extension class ([70bdc68](https://github.com/ReVanced/revanced-patches/commit/70bdc6840d465399625aa1ae0259f49e72711955))
|
||||||
|
* **Sync for Reddit:** Fix patches by using correct extension name ([030093e](https://github.com/ReVanced/revanced-patches/commit/030093e913aab3fab43935eedbaeba0f6c0491bb))
|
||||||
|
* **Twitter:** Merge correct extension by depending on correct extension patch ([8281cf6](https://github.com/ReVanced/revanced-patches/commit/8281cf6a3eead8cc25a277371e0b0ab2be982497))
|
||||||
|
* **YouTube - Spoof video streams:** Add missing preferred language preference to the settings ([630633c](https://github.com/ReVanced/revanced-patches/commit/630633cf57c65c65e5578046413e17670ae336e8))
|
||||||
|
* **YouTube - Spoof video streams:** Enable opus codec by updating iOS client version ([#4063](https://github.com/ReVanced/revanced-patches/issues/4063)) ([0af156f](https://github.com/ReVanced/revanced-patches/commit/0af156f18972c5f089af4bb69824968d2a47d18f))
|
||||||
|
* **YouTube - Spoof video streams:** Update `Force AVC` client data ([#4064](https://github.com/ReVanced/revanced-patches/issues/4064)) ([7d537dd](https://github.com/ReVanced/revanced-patches/commit/7d537ddff4bb5421fa320741275131a66ef5c7bb))
|
||||||
|
* **YouTube Music - Permanent shuffle:** Remove obsolete and non functional patch ([#4073](https://github.com/ReVanced/revanced-patches/issues/4073)) ([fbc6ab6](https://github.com/ReVanced/revanced-patches/commit/fbc6ab6a357b351f02d4d486ddc2072cf53199c3))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Nyx:** Remove broken `Unlock pro` patch ([1fe8b16](https://github.com/ReVanced/revanced-patches/commit/1fe8b164eab0c4fa80ab2da2581977f5111a2858))
|
||||||
|
* **YouTube - Spoof video streams:** Allow picking a default audio language track ([#4050](https://github.com/ReVanced/revanced-patches/issues/4050)) ([ede666b](https://github.com/ReVanced/revanced-patches/commit/ede666b5cb64fcbaa1334ad8bef79e2634ced113))
|
||||||
|
* **YouTube Music:** Add `Spoof video streams` patch to fix playback ([#4065](https://github.com/ReVanced/revanced-patches/issues/4065)) ([cf3116a](https://github.com/ReVanced/revanced-patches/commit/cf3116a7583d09c25c798a85687a056f143656f0))
|
||||||
|
* **YouTube:** Add `Open videos fullscreen` patch ([#4069](https://github.com/ReVanced/revanced-patches/issues/4069)) ([296d63b](https://github.com/ReVanced/revanced-patches/commit/296d63bd42c338a01efbcb2df702e5822d05a5f1))
|
||||||
|
|
||||||
|
# [5.3.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.6...v5.3.0-dev.7) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Add missing preferred language preference to the settings ([630633c](https://github.com/ReVanced/revanced-patches/commit/630633cf57c65c65e5578046413e17670ae336e8))
|
||||||
|
|
||||||
|
# [5.3.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.5...v5.3.0-dev.6) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Allow picking a default audio language track ([#4050](https://github.com/ReVanced/revanced-patches/issues/4050)) ([ede666b](https://github.com/ReVanced/revanced-patches/commit/ede666b5cb64fcbaa1334ad8bef79e2634ced113))
|
||||||
|
|
||||||
|
# [5.3.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.4...v5.3.0-dev.5) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Change package name:** Prevent applying the patch to known incompatible apps ([#3943](https://github.com/ReVanced/revanced-patches/issues/3943)) ([44936e7](https://github.com/ReVanced/revanced-patches/commit/44936e71e846f72f7279950232a5dba37765ceb3))
|
||||||
|
* **YouTube Music - Permanent shuffle:** Remove obsolete and non functional patch ([#4073](https://github.com/ReVanced/revanced-patches/issues/4073)) ([fbc6ab6](https://github.com/ReVanced/revanced-patches/commit/fbc6ab6a357b351f02d4d486ddc2072cf53199c3))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **YouTube:** Add `Open videos fullscreen` patch ([#4069](https://github.com/ReVanced/revanced-patches/issues/4069)) ([296d63b](https://github.com/ReVanced/revanced-patches/commit/296d63bd42c338a01efbcb2df702e5822d05a5f1))
|
||||||
|
|
||||||
|
# [5.3.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.3...v5.3.0-dev.4) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **Nyx:** Remove broken `Unlock pro` patch ([1fe8b16](https://github.com/ReVanced/revanced-patches/commit/1fe8b164eab0c4fa80ab2da2581977f5111a2858))
|
||||||
|
|
||||||
|
# [5.3.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.2...v5.3.0-dev.3) (2024-12-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **YouTube - Spoof video streams:** Update `Force AVC` client data ([#4064](https://github.com/ReVanced/revanced-patches/issues/4064)) ([7d537dd](https://github.com/ReVanced/revanced-patches/commit/7d537ddff4bb5421fa320741275131a66ef5c7bb))
|
||||||
|
|
||||||
|
# [5.3.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.3.0-dev.1...v5.3.0-dev.2) (2024-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **Reddit:** Fix patches by using correct extension class ([70bdc68](https://github.com/ReVanced/revanced-patches/commit/70bdc6840d465399625aa1ae0259f49e72711955))
|
||||||
|
|
||||||
# [5.3.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.2.4-dev.3...v5.3.0-dev.1) (2024-12-08)
|
# [5.3.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.2.4-dev.3...v5.3.0-dev.1) (2024-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
android.namespace = "app.revanced.extension"
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import android.os.Handler;
|
|||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
/** @noinspection deprecation, unused */
|
@SuppressWarnings({"deprecation", "unused"})
|
||||||
public class SpoofWifiPatch {
|
public class SpoofWifiPatch {
|
||||||
|
|
||||||
// Used to check what the (real or fake) active network is (take a look at `hasTransport`).
|
// Used to check what the (real or fake) active network is (take a look at `hasTransport`).
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(libs.annotation)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,336 @@
|
|||||||
|
package app.revanced.extension.all.misc.directory.documentsprovider;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.ProviderInfo;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.os.CancellationSignal;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
|
import android.provider.DocumentsProvider;
|
||||||
|
import android.system.ErrnoException;
|
||||||
|
import android.system.Os;
|
||||||
|
import android.system.StructStat;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A DocumentsProvider that allows access to the app's internal data directory.
|
||||||
|
*/
|
||||||
|
@SuppressLint("LongLogTag")
|
||||||
|
public class InternalDataDocumentsProvider extends DocumentsProvider {
|
||||||
|
private static final String[] rootColumns =
|
||||||
|
{"root_id", "mime_types", "flags", "icon", "title", "summary", "document_id"};
|
||||||
|
private static final String[] directoryColumns =
|
||||||
|
{"document_id", "mime_type", "_display_name", "last_modified", "flags",
|
||||||
|
"_size", "full_path", "lstat_info"};
|
||||||
|
private static final int S_IFLNK = 0x8000;
|
||||||
|
|
||||||
|
private String packageName;
|
||||||
|
private File dataDirectory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively delete a file or directory and all its children.
|
||||||
|
*
|
||||||
|
* @param root The file or directory to delete.
|
||||||
|
* @return True if the file or directory and all its children were successfully deleted.
|
||||||
|
*/
|
||||||
|
private static boolean deleteRecursively(File root) {
|
||||||
|
// If root is a directory, delete all children first
|
||||||
|
if (root.isDirectory()) {
|
||||||
|
try {
|
||||||
|
// Only delete recursively if the directory is not a symlink
|
||||||
|
if ((Os.lstat(root.getPath()).st_mode & S_IFLNK) != S_IFLNK) {
|
||||||
|
File[] files = root.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
if (!deleteRecursively(file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ErrnoException e) {
|
||||||
|
Log.e("InternalDocumentsProvider", "Failed to lstat " + root.getPath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete file or empty directory
|
||||||
|
return root.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the MIME type of a file based on its extension.
|
||||||
|
*
|
||||||
|
* @param file The file to resolve the MIME type for.
|
||||||
|
* @return The MIME type of the file.
|
||||||
|
*/
|
||||||
|
private static String resolveMimeType(File file) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
return DocumentsContract.Document.MIME_TYPE_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = file.getName();
|
||||||
|
int indexOfExtDot = name.lastIndexOf('.');
|
||||||
|
if (indexOfExtDot < 0) {
|
||||||
|
// No extension
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
String extension = name.substring(indexOfExtDot + 1).toLowerCase();
|
||||||
|
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||||
|
return mimeType != null ? mimeType : "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean onCreate() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void attachInfo(Context context, ProviderInfo providerInfo) {
|
||||||
|
super.attachInfo(context, providerInfo);
|
||||||
|
|
||||||
|
this.packageName = context.getPackageName();
|
||||||
|
this.dataDirectory = context.getFilesDir().getParentFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String createDocument(String parentDocumentId, String mimeType, String displayName) throws FileNotFoundException {
|
||||||
|
File directory = resolveDocumentId(parentDocumentId);
|
||||||
|
File file = new File(directory, displayName);
|
||||||
|
|
||||||
|
// If file already exists, append a number to the name
|
||||||
|
int i = 2;
|
||||||
|
while (file.exists()) {
|
||||||
|
file = new File(directory, displayName + " (" + i + ")");
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create the file or directory
|
||||||
|
if (mimeType.equals(DocumentsContract.Document.MIME_TYPE_DIR) ? file.mkdir() : file.createNewFile()) {
|
||||||
|
// Return the document ID of the new entity
|
||||||
|
if (!parentDocumentId.endsWith("/")) {
|
||||||
|
parentDocumentId = parentDocumentId + "/";
|
||||||
|
}
|
||||||
|
return parentDocumentId + file.getName();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
// Do nothing. We are throwing a FileNotFoundException later if the file could not be created.
|
||||||
|
}
|
||||||
|
throw new FileNotFoundException("Failed to create document in " + parentDocumentId + " with name " + displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void deleteDocument(String documentId) throws FileNotFoundException {
|
||||||
|
File file = resolveDocumentId(documentId);
|
||||||
|
if (!deleteRecursively(file)) {
|
||||||
|
throw new FileNotFoundException("Failed to delete document " + documentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String getDocumentType(String documentId) throws FileNotFoundException {
|
||||||
|
return resolveMimeType(resolveDocumentId(documentId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||||
|
return documentId.startsWith(parentDocumentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId) throws FileNotFoundException {
|
||||||
|
File source = resolveDocumentId(sourceDocumentId);
|
||||||
|
File dest = resolveDocumentId(targetParentDocumentId);
|
||||||
|
|
||||||
|
File file = new File(dest, source.getName());
|
||||||
|
if (!file.exists() && source.renameTo(file)) {
|
||||||
|
// Return the new document ID
|
||||||
|
if (targetParentDocumentId.endsWith("/")) {
|
||||||
|
return targetParentDocumentId + file.getName();
|
||||||
|
}
|
||||||
|
return targetParentDocumentId + "/" + file.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileNotFoundException("Failed to move document from " + sourceDocumentId + " to " + targetParentDocumentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException {
|
||||||
|
File file = resolveDocumentId(documentId);
|
||||||
|
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.parseMode(mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException {
|
||||||
|
if (parentDocumentId.endsWith("/")) {
|
||||||
|
parentDocumentId = parentDocumentId.substring(0, parentDocumentId.length() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projection == null) {
|
||||||
|
projection = directoryColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixCursor cursor = new MatrixCursor(projection);
|
||||||
|
File children = resolveDocumentId(parentDocumentId);
|
||||||
|
|
||||||
|
// Collect all children
|
||||||
|
File[] files = children.listFiles();
|
||||||
|
if (files != null) {
|
||||||
|
for (File file : files) {
|
||||||
|
addRowForDocument(cursor, parentDocumentId + "/" + file.getName(), file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||||
|
if (projection == null) {
|
||||||
|
projection = directoryColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixCursor cursor = new MatrixCursor(projection);
|
||||||
|
addRowForDocument(cursor, documentId, null);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Cursor queryRoots(String[] projection) {
|
||||||
|
ApplicationInfo info = Objects.requireNonNull(getContext()).getApplicationInfo();
|
||||||
|
String appName = info.loadLabel(getContext().getPackageManager()).toString();
|
||||||
|
|
||||||
|
if (projection == null) {
|
||||||
|
projection = rootColumns;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixCursor cursor = new MatrixCursor(projection);
|
||||||
|
MatrixCursor.RowBuilder row = cursor.newRow();
|
||||||
|
row.add(DocumentsContract.Root.COLUMN_ROOT_ID, this.packageName);
|
||||||
|
row.add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, this.packageName);
|
||||||
|
row.add(DocumentsContract.Root.COLUMN_SUMMARY, this.packageName);
|
||||||
|
row.add(DocumentsContract.Root.COLUMN_FLAGS,
|
||||||
|
DocumentsContract.Root.FLAG_LOCAL_ONLY |
|
||||||
|
DocumentsContract.Root.FLAG_SUPPORTS_IS_CHILD);
|
||||||
|
row.add(DocumentsContract.Root.COLUMN_TITLE, appName);
|
||||||
|
row.add(DocumentsContract.Root.COLUMN_MIME_TYPES, "*/*");
|
||||||
|
row.add(DocumentsContract.Root.COLUMN_ICON, info.icon);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void removeDocument(String documentId, String parentDocumentId) throws FileNotFoundException {
|
||||||
|
deleteDocument(documentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String renameDocument(String documentId, String displayName) throws FileNotFoundException {
|
||||||
|
File file = resolveDocumentId(documentId);
|
||||||
|
if (!file.renameTo(new File(file.getParentFile(), displayName))) {
|
||||||
|
throw new FileNotFoundException("Failed to rename document from " + documentId + " to " + displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the new document ID
|
||||||
|
return documentId.substring(0, documentId.lastIndexOf('/', documentId.length() - 2)) + "/" + displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a file instance for a given document ID.
|
||||||
|
*
|
||||||
|
* @param fullContentPath The document ID to resolve.
|
||||||
|
* @return File object for the given document ID.
|
||||||
|
* @throws FileNotFoundException If the document ID is invalid or the file does not exist.
|
||||||
|
*/
|
||||||
|
private File resolveDocumentId(String fullContentPath) throws FileNotFoundException {
|
||||||
|
if (!fullContentPath.startsWith(this.packageName)) {
|
||||||
|
throw new FileNotFoundException(fullContentPath + " not found");
|
||||||
|
}
|
||||||
|
String path = fullContentPath.substring(this.packageName.length());
|
||||||
|
|
||||||
|
// Resolve the relative path within /data/data/{PKG}
|
||||||
|
File file;
|
||||||
|
if (path.equals("/") || path.isEmpty()) {
|
||||||
|
file = this.dataDirectory;
|
||||||
|
} else {
|
||||||
|
// Remove leading slash
|
||||||
|
String relativePath = path.substring(1);
|
||||||
|
file = new File(this.dataDirectory, relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw new FileNotFoundException(fullContentPath + " not found");
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a row containing all file properties to a MatrixCursor for a given document ID.
|
||||||
|
*
|
||||||
|
* @param cursor The cursor to add the row to.
|
||||||
|
* @param documentId The document ID to add the row for.
|
||||||
|
* @param file The file to add the row for. If null, the file will be resolved from the document ID.
|
||||||
|
* @throws FileNotFoundException If the file does not exist.
|
||||||
|
*/
|
||||||
|
private void addRowForDocument(MatrixCursor cursor, String documentId, File file) throws FileNotFoundException {
|
||||||
|
if (file == null) {
|
||||||
|
file = resolveDocumentId(documentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int flags = 0;
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
// Prefer list view for directories
|
||||||
|
flags = flags | DocumentsContract.Document.FLAG_DIR_PREFERS_LAST_MODIFIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.canWrite()) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
flags = flags | DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||||
|
}
|
||||||
|
|
||||||
|
flags = flags | DocumentsContract.Document.FLAG_SUPPORTS_WRITE |
|
||||||
|
DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
|
||||||
|
DocumentsContract.Document.FLAG_SUPPORTS_RENAME |
|
||||||
|
DocumentsContract.Document.FLAG_SUPPORTS_MOVE;
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixCursor.RowBuilder row = cursor.newRow();
|
||||||
|
row.add(DocumentsContract.Document.COLUMN_DOCUMENT_ID, documentId);
|
||||||
|
row.add(DocumentsContract.Document.COLUMN_DISPLAY_NAME, file.getName());
|
||||||
|
row.add(DocumentsContract.Document.COLUMN_SIZE, file.length());
|
||||||
|
row.add(DocumentsContract.Document.COLUMN_MIME_TYPE, resolveMimeType(file));
|
||||||
|
row.add(DocumentsContract.Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||||
|
row.add(DocumentsContract.Document.COLUMN_FLAGS, flags);
|
||||||
|
|
||||||
|
// Custom columns
|
||||||
|
row.add("full_path", file.getAbsolutePath());
|
||||||
|
|
||||||
|
// Add lstat column
|
||||||
|
String path = file.getPath();
|
||||||
|
try {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
StructStat lstat = Os.lstat(path);
|
||||||
|
sb.append(lstat.st_mode);
|
||||||
|
sb.append(";");
|
||||||
|
sb.append(lstat.st_uid);
|
||||||
|
sb.append(";");
|
||||||
|
sb.append(lstat.st_gid);
|
||||||
|
// Append symlink target if it is a symlink
|
||||||
|
if ((lstat.st_mode & S_IFLNK) == S_IFLNK) {
|
||||||
|
sb.append(";");
|
||||||
|
sb.append(Os.readlink(path));
|
||||||
|
}
|
||||||
|
row.add("lstat_info", sb.toString());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.e("InternalDocumentsProvider", "Failed to get lstat info for " + path, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,15 @@
|
|||||||
android.namespace = "app.revanced.extension"
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import android.os.Build;
|
|||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
public final class RemoveScreencaptureRestrictionPatch {
|
@SuppressWarnings("unused")
|
||||||
|
public final class RemoveScreenCaptureRestrictionPatch {
|
||||||
// Member of AudioAttributes.Builder
|
// Member of AudioAttributes.Builder
|
||||||
@RequiresApi(api = Build.VERSION_CODES.Q)
|
@RequiresApi(api = Build.VERSION_CODES.Q)
|
||||||
public static AudioAttributes.Builder setAllowedCapturePolicy(final AudioAttributes.Builder builder, final int capturePolicy) {
|
public static AudioAttributes.Builder setAllowedCapturePolicy(final AudioAttributes.Builder builder, final int capturePolicy) {
|
||||||
@@ -1 +1,16 @@
|
|||||||
android.namespace = "app.revanced.extension"
|
android {
|
||||||
|
namespace = "app.revanced.extension"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
compileOnly(libs.annotation)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package app.revanced.extension.all.screenshot.removerestriction;
|
|||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class RemoveScreenshotRestrictionPatch {
|
public class RemoveScreenshotRestrictionPatch {
|
||||||
|
|
||||||
public static void addFlags(Window window, int flags) {
|
public static void addFlags(Window window, int flags) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.revanced.extension"
|
namespace = "app.revanced.extension"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|||||||
5
extensions/music/build.gradle.kts
Normal file
5
extensions/music/build.gradle.kts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
}
|
||||||
1
extensions/music/src/main/AndroidManifest.xml
Normal file
1
extensions/music/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package app.revanced.extension.music.spoof;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @noinspection unused
|
||||||
|
*/
|
||||||
|
public class SpoofClientPatch {
|
||||||
|
private static final int CLIENT_TYPE_ID = 26;
|
||||||
|
private static final String CLIENT_VERSION = "6.21";
|
||||||
|
private static final String DEVICE_MODEL = "iPhone16,2";
|
||||||
|
private static final String OS_VERSION = "17.7.2.21H221";
|
||||||
|
|
||||||
|
public static int getClientId() {
|
||||||
|
return CLIENT_TYPE_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientVersion() {
|
||||||
|
return CLIENT_VERSION;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getClientModel() {
|
||||||
|
return DEVICE_MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getOsVersion() {
|
||||||
|
return OS_VERSION;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
package app.revanced.extension.patches;
|
package app.revanced.extension.reddit.patches;
|
||||||
|
|
||||||
import com.reddit.domain.model.ILink;
|
import com.reddit.domain.model.ILink;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public final class FilterPromotedLinksPatch {
|
public final class FilterPromotedLinksPatch {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
* Filters list from promoted links.
|
* Filters list from promoted links.
|
||||||
**/
|
**/
|
||||||
public static List<?> filterChildren(final Iterable<?> links) {
|
public static List<?> filterChildren(final Iterable<?> links) {
|
||||||
@@ -4,7 +4,7 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.revanced.extension"
|
namespace = "app.revanced.extension"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ import java.net.URL;
|
|||||||
* @noinspection unused
|
* @noinspection unused
|
||||||
*/
|
*/
|
||||||
public class GmsCoreSupport {
|
public class GmsCoreSupport {
|
||||||
public static final String ORIGINAL_UNPATCHED_PACKAGE_NAME = "com.google.android.youtube";
|
private static final String PACKAGE_NAME_YOUTUBE = "com.google.android.youtube";
|
||||||
|
private static final String PACKAGE_NAME_YOUTUBE_MUSIC = "com.google.android.apps.youtube.music";
|
||||||
|
|
||||||
private static final String GMS_CORE_PACKAGE_NAME
|
private static final String GMS_CORE_PACKAGE_NAME
|
||||||
= getGmsCoreVendorGroupId() + ".android.gms";
|
= getGmsCoreVendorGroupId() + ".android.gms";
|
||||||
private static final Uri GMS_CORE_PROVIDER
|
private static final Uri GMS_CORE_PROVIDER
|
||||||
@@ -52,17 +54,20 @@ public class GmsCoreSupport {
|
|||||||
|
|
||||||
private static void showBatteryOptimizationDialog(Activity context,
|
private static void showBatteryOptimizationDialog(Activity context,
|
||||||
String dialogMessageRef,
|
String dialogMessageRef,
|
||||||
String positiveButtonStringRef,
|
String positiveButtonTextRef,
|
||||||
DialogInterface.OnClickListener onPositiveClickListener) {
|
DialogInterface.OnClickListener onPositiveClickListener) {
|
||||||
// Do not set cancelable to false, to allow using back button to skip the action,
|
// Use a delay to allow the activity to finish initializing.
|
||||||
// just in case the check can never be satisfied.
|
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
||||||
var dialog = new AlertDialog.Builder(context)
|
Utils.runOnMainThreadDelayed(() -> {
|
||||||
.setIconAttribute(android.R.attr.alertDialogIcon)
|
// Do not set cancelable to false, to allow using back button to skip the action,
|
||||||
.setTitle(str("gms_core_dialog_title"))
|
// just in case the battery change can never be satisfied.
|
||||||
.setMessage(str(dialogMessageRef))
|
var dialog = new AlertDialog.Builder(context)
|
||||||
.setPositiveButton(str(positiveButtonStringRef), onPositiveClickListener)
|
.setTitle(str("gms_core_dialog_title"))
|
||||||
.create();
|
.setMessage(str(dialogMessageRef))
|
||||||
Utils.showDialog(context, dialog);
|
.setPositiveButton(str(positiveButtonTextRef), onPositiveClickListener)
|
||||||
|
.create();
|
||||||
|
Utils.showDialog(context, dialog);
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,7 +79,8 @@ public class GmsCoreSupport {
|
|||||||
// Verify the user has not included GmsCore for a root installation.
|
// Verify the user has not included GmsCore for a root installation.
|
||||||
// GmsCore Support changes the package name, but with a mounted installation
|
// GmsCore Support changes the package name, but with a mounted installation
|
||||||
// all manifest changes are ignored and the original package name is used.
|
// all manifest changes are ignored and the original package name is used.
|
||||||
if (context.getPackageName().equals(ORIGINAL_UNPATCHED_PACKAGE_NAME)) {
|
String packageName = context.getPackageName();
|
||||||
|
if (packageName.equals(PACKAGE_NAME_YOUTUBE) || packageName.equals(PACKAGE_NAME_YOUTUBE_MUSIC)) {
|
||||||
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
|
Logger.printInfo(() -> "App is mounted with root, but GmsCore patch was included");
|
||||||
// Cannot use localize text here, since the app will load
|
// Cannot use localize text here, since the app will load
|
||||||
// resources from the unpatched app and all patch strings are missing.
|
// resources from the unpatched app and all patch strings are missing.
|
||||||
@@ -99,7 +105,22 @@ public class GmsCoreSupport {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if GmsCore is running in the background.
|
// Check if GmsCore is whitelisted from battery optimizations.
|
||||||
|
if (isAndroidAutomotive(context)) {
|
||||||
|
// Ignore Android Automotive devices (Google built-in),
|
||||||
|
// as there is no way to disable battery optimizations.
|
||||||
|
Logger.printDebug(() -> "Device is Android Automotive");
|
||||||
|
} else if (batteryOptimizationsEnabled(context)) {
|
||||||
|
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
|
||||||
|
|
||||||
|
showBatteryOptimizationDialog(context,
|
||||||
|
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
||||||
|
"gms_core_dialog_continue_text",
|
||||||
|
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if GmsCore is currently running in the background.
|
||||||
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||||
@@ -108,18 +129,8 @@ public class GmsCoreSupport {
|
|||||||
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
||||||
"gms_core_dialog_open_website_text",
|
"gms_core_dialog_open_website_text",
|
||||||
(dialog, id) -> open(DONT_KILL_MY_APP_LINK));
|
(dialog, id) -> open(DONT_KILL_MY_APP_LINK));
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if GmsCore is whitelisted from battery optimizations.
|
|
||||||
if (batteryOptimizationsEnabled(context)) {
|
|
||||||
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
|
|
||||||
showBatteryOptimizationDialog(context,
|
|
||||||
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
|
||||||
"gms_core_dialog_continue_text",
|
|
||||||
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "checkGmsCore failure", ex);
|
Logger.printException(() -> "checkGmsCore failure", ex);
|
||||||
}
|
}
|
||||||
@@ -140,15 +151,17 @@ public class GmsCoreSupport {
|
|||||||
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isAndroidAutomotive(Context context) {
|
||||||
|
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
private static String getGmsCoreDownload() {
|
private static String getGmsCoreDownload() {
|
||||||
final var vendorGroupId = getGmsCoreVendorGroupId();
|
final var vendorGroupId = getGmsCoreVendorGroupId();
|
||||||
//noinspection SwitchStatementWithTooFewBranches
|
//noinspection SwitchStatementWithTooFewBranches
|
||||||
switch (vendorGroupId) {
|
return switch (vendorGroupId) {
|
||||||
case "app.revanced":
|
case "app.revanced" -> "https://github.com/revanced/gmscore/releases/latest";
|
||||||
return "https://github.com/revanced/gmscore/releases/latest";
|
default -> vendorGroupId + ".android.gms";
|
||||||
default:
|
};
|
||||||
return vendorGroupId + ".android.gms";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modified by a patch. Do not touch.
|
// Modified by a patch. Do not touch.
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import android.annotation.SuppressLint;
|
|||||||
import android.app.*;
|
import android.app.*;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Configuration;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -38,15 +40,18 @@ import java.util.concurrent.SynchronousQueue;
|
|||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
private static Context context;
|
private static volatile Context context;
|
||||||
|
|
||||||
private static String versionName;
|
private static String versionName;
|
||||||
|
private static String applicationLabel;
|
||||||
|
|
||||||
private Utils() {
|
private Utils() {
|
||||||
} // utility class
|
} // utility class
|
||||||
@@ -61,28 +66,30 @@ public class Utils {
|
|||||||
return ""; // Value is replaced during patching.
|
return ""; // Value is replaced during patching.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static PackageInfo getPackageInfo() throws PackageManager.NameNotFoundException {
|
||||||
|
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
||||||
|
|
||||||
|
PackageManager packageManager = context.getPackageManager();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
return packageManager.getPackageInfo(
|
||||||
|
packageName,
|
||||||
|
PackageManager.PackageInfoFlags.of(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageManager.getPackageInfo(
|
||||||
|
packageName,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The version name of the app, such as 19.11.43
|
* @return The version name of the app, such as 19.11.43
|
||||||
*/
|
*/
|
||||||
public static String getAppVersionName() {
|
public static String getAppVersionName() {
|
||||||
if (versionName == null) {
|
if (versionName == null) {
|
||||||
try {
|
try {
|
||||||
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
versionName = getPackageInfo().versionName;
|
||||||
|
|
||||||
PackageManager packageManager = context.getPackageManager();
|
|
||||||
PackageInfo packageInfo;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
packageInfo = packageManager.getPackageInfo(
|
|
||||||
packageName,
|
|
||||||
PackageManager.PackageInfoFlags.of(0)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
packageInfo = packageManager.getPackageInfo(
|
|
||||||
packageName,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
versionName = packageInfo.versionName;
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "Failed to get package info", ex);
|
Logger.printException(() -> "Failed to get package info", ex);
|
||||||
versionName = "Unknown";
|
versionName = "Unknown";
|
||||||
@@ -92,6 +99,19 @@ public class Utils {
|
|||||||
return versionName;
|
return versionName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getApplicationName() {
|
||||||
|
if (applicationLabel == null) {
|
||||||
|
try {
|
||||||
|
ApplicationInfo applicationInfo = getPackageInfo().applicationInfo;
|
||||||
|
applicationLabel = (String) applicationInfo.loadLabel(context.getPackageManager());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Failed to get application name", ex);
|
||||||
|
applicationLabel = "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicationLabel;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide a view by setting its layout height and width to 1dp.
|
* Hide a view by setting its layout height and width to 1dp.
|
||||||
@@ -325,7 +345,7 @@ public class Utils {
|
|||||||
|
|
||||||
public static void restartApp(@NonNull Context context) {
|
public static void restartApp(@NonNull Context context) {
|
||||||
String packageName = context.getPackageName();
|
String packageName = context.getPackageName();
|
||||||
Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
|
Intent intent = Objects.requireNonNull(context.getPackageManager().getLaunchIntentForPackage(packageName));
|
||||||
Intent mainIntent = Intent.makeRestartActivityTask(intent.getComponent());
|
Intent mainIntent = Intent.makeRestartActivityTask(intent.getComponent());
|
||||||
// Required for API 34 and later
|
// Required for API 34 and later
|
||||||
// Ref: https://developer.android.com/about/versions/14/behavior-changes-14#safer-intents
|
// Ref: https://developer.android.com/about/versions/14/behavior-changes-14#safer-intents
|
||||||
@@ -342,7 +362,17 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void setContext(Context appContext) {
|
public static void setContext(Context appContext) {
|
||||||
|
// Must initially set context as the language settings needs it.
|
||||||
context = appContext;
|
context = appContext;
|
||||||
|
|
||||||
|
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||||
|
if (language != AppLanguage.DEFAULT) {
|
||||||
|
// Create a new context with the desired 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.
|
// 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,
|
// Calling the regular printDebug method here can cause a Settings context null pointer exception,
|
||||||
// even though the context is already set before the call.
|
// even though the context is already set before the call.
|
||||||
@@ -499,6 +529,17 @@ public class Utils {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isDarkModeEnabled(Context context) {
|
||||||
|
Configuration config = context.getResources().getConfiguration();
|
||||||
|
final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||||
|
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLandscapeOrientation() {
|
||||||
|
final int orientation = context.getResources().getConfiguration().orientation;
|
||||||
|
return orientation == Configuration.ORIENTATION_LANDSCAPE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Automatically logs any exceptions the runnable throws.
|
* Automatically logs any exceptions the runnable throws.
|
||||||
*
|
*
|
||||||
@@ -571,7 +612,7 @@ public class Utils {
|
|||||||
|| networkType == NetworkType.OTHER;
|
|| networkType == NetworkType.OTHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("MissingPermission") // permission already included in YouTube
|
@SuppressLint({"MissingPermission", "deprecation"}) // Permission already included in YouTube.
|
||||||
public static NetworkType getNetworkType() {
|
public static NetworkType getNetworkType() {
|
||||||
Context networkContext = getContext();
|
Context networkContext = getContext();
|
||||||
if (networkContext == null) {
|
if (networkContext == null) {
|
||||||
@@ -681,8 +722,8 @@ public class Utils {
|
|||||||
Preference preference = group.getPreference(i);
|
Preference preference = group.getPreference(i);
|
||||||
|
|
||||||
final Sort preferenceSort;
|
final Sort preferenceSort;
|
||||||
if (preference instanceof PreferenceGroup) {
|
if (preference instanceof PreferenceGroup subGroup) {
|
||||||
sortPreferenceGroups((PreferenceGroup) preference);
|
sortPreferenceGroups(subGroup);
|
||||||
preferenceSort = groupSort; // Sort value for groups is for it's content, not itself.
|
preferenceSort = groupSort; // Sort value for groups is for it's content, not itself.
|
||||||
} else {
|
} else {
|
||||||
// Allow individual preferences to set a key sorting.
|
// Allow individual preferences to set a key sorting.
|
||||||
@@ -736,8 +777,8 @@ public class Utils {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String deviceLanguage = Utils.getContext().getResources().getConfiguration().locale.getLanguage();
|
String revancedLocale = Utils.getContext().getResources().getConfiguration().locale.getLanguage();
|
||||||
if (deviceLanguage.equals("en")) {
|
if (revancedLocale.equals(Locale.ENGLISH.getLanguage())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,8 +786,8 @@ public class Utils {
|
|||||||
Preference pref = group.getPreference(i);
|
Preference pref = group.getPreference(i);
|
||||||
pref.setSingleLineTitle(false);
|
pref.setSingleLineTitle(false);
|
||||||
|
|
||||||
if (pref instanceof PreferenceGroup) {
|
if (pref instanceof PreferenceGroup subGroup) {
|
||||||
setPreferenceTitlesToMultiLineIfNeeded((PreferenceGroup) pref);
|
setPreferenceTitlesToMultiLineIfNeeded(subGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package app.revanced.extension.shared.settings;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public enum AppLanguage {
|
||||||
|
/**
|
||||||
|
* The current app language.
|
||||||
|
*/
|
||||||
|
DEFAULT,
|
||||||
|
|
||||||
|
// Language codes found in locale_config.xml
|
||||||
|
// All region specific variants have been removed.
|
||||||
|
AF,
|
||||||
|
AM,
|
||||||
|
AR,
|
||||||
|
AS,
|
||||||
|
AZ,
|
||||||
|
BE,
|
||||||
|
BG,
|
||||||
|
BN,
|
||||||
|
BS,
|
||||||
|
CA,
|
||||||
|
CS,
|
||||||
|
DA,
|
||||||
|
DE,
|
||||||
|
EL,
|
||||||
|
EN,
|
||||||
|
ES,
|
||||||
|
ET,
|
||||||
|
EU,
|
||||||
|
FA,
|
||||||
|
FI,
|
||||||
|
FR,
|
||||||
|
GL,
|
||||||
|
GU,
|
||||||
|
HI,
|
||||||
|
HE, // App uses obsolete 'IW' and not the modern 'HE' ISO code.
|
||||||
|
HR,
|
||||||
|
HU,
|
||||||
|
HY,
|
||||||
|
ID,
|
||||||
|
IS,
|
||||||
|
IT,
|
||||||
|
JA,
|
||||||
|
KA,
|
||||||
|
KK,
|
||||||
|
KM,
|
||||||
|
KN,
|
||||||
|
KO,
|
||||||
|
KY,
|
||||||
|
LO,
|
||||||
|
LT,
|
||||||
|
LV,
|
||||||
|
MK,
|
||||||
|
ML,
|
||||||
|
MN,
|
||||||
|
MR,
|
||||||
|
MS,
|
||||||
|
MY,
|
||||||
|
NE,
|
||||||
|
NL,
|
||||||
|
NB,
|
||||||
|
OR,
|
||||||
|
PA,
|
||||||
|
PL,
|
||||||
|
PT,
|
||||||
|
RO,
|
||||||
|
RU,
|
||||||
|
SI,
|
||||||
|
SK,
|
||||||
|
SL,
|
||||||
|
SQ,
|
||||||
|
SR,
|
||||||
|
SV,
|
||||||
|
SW,
|
||||||
|
TA,
|
||||||
|
TE,
|
||||||
|
TH,
|
||||||
|
TL,
|
||||||
|
TR,
|
||||||
|
UK,
|
||||||
|
UR,
|
||||||
|
UZ,
|
||||||
|
VI,
|
||||||
|
ZH,
|
||||||
|
ZU;
|
||||||
|
|
||||||
|
private final String language;
|
||||||
|
|
||||||
|
AppLanguage() {
|
||||||
|
language = name().toLowerCase(Locale.US);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The 2 letter ISO 639_1 language code.
|
||||||
|
*/
|
||||||
|
public String getLanguage() {
|
||||||
|
// Changing the app language does not force the app to completely restart,
|
||||||
|
// so the default needs to be the current language and not a static field.
|
||||||
|
if (this == DEFAULT) {
|
||||||
|
return Locale.getDefault().getLanguage();
|
||||||
|
}
|
||||||
|
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Locale getLocale() {
|
||||||
|
if (this == DEFAULT) {
|
||||||
|
return Locale.getDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Locale.forLanguageTag(language);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package app.revanced.extension.shared.settings;
|
package app.revanced.extension.shared.settings;
|
||||||
|
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
|
||||||
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
|
||||||
|
|
||||||
import static java.lang.Boolean.FALSE;
|
import static java.lang.Boolean.FALSE;
|
||||||
import static java.lang.Boolean.TRUE;
|
import static java.lang.Boolean.TRUE;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
|
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
|
||||||
|
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.SpoofiOSAvailability;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings shared across multiple apps.
|
* Settings shared across multiple apps.
|
||||||
@@ -20,9 +21,14 @@ public class BaseSettings {
|
|||||||
|
|
||||||
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
|
public static final IntegerSetting CHECK_ENVIRONMENT_WARNINGS_ISSUED = new IntegerSetting("revanced_check_environment_warnings_issued", 0, true, false);
|
||||||
|
|
||||||
|
public static final EnumSetting<AppLanguage> REVANCED_LANGUAGE = new EnumSetting<>("revanced_language", AppLanguage.DEFAULT, true, "revanced_language_user_dialog_message");
|
||||||
|
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
|
||||||
|
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
|
||||||
|
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
|
||||||
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
|
||||||
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofVideoStreamsPatch.ForceiOSAVCAvailability());
|
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
|
||||||
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
|
// Client type must be last spoof setting due to cyclic references.
|
||||||
|
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_UNPLUGGED, true, parent(SPOOF_VIDEO_STREAMS));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ public class BooleanSetting extends Setting<Boolean> {
|
|||||||
*/
|
*/
|
||||||
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
||||||
setting.value = Objects.requireNonNull(newValue);
|
setting.value = Objects.requireNonNull(newValue);
|
||||||
|
|
||||||
|
if (setting.isSetToDefault()) {
|
||||||
|
setting.removeFromPreferences();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,10 +69,8 @@ public class BooleanSetting extends Setting<Boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull Boolean newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveBoolean(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveBoolean(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -89,10 +89,8 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull T newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveEnumAsString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveEnumAsString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ public class FloatSetting extends Setting<Float> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull Float newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveFloatString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveFloatString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ public class IntegerSetting extends Setting<Integer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull Integer newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveIntegerString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveIntegerString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ public class LongSetting extends Setting<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull Long newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveLongString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveLongString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import app.revanced.extension.shared.Logger;
|
|||||||
import app.revanced.extension.shared.StringRef;
|
import app.revanced.extension.shared.StringRef;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
|
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ import java.util.*;
|
|||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public abstract class Setting<T> {
|
public abstract class Setting<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -154,7 +152,6 @@ public abstract class Setting<T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Confirmation message to display, if the user tries to change the setting from the default value.
|
* Confirmation message to display, if the user tries to change the setting from the default value.
|
||||||
* Currently this works only for Boolean setting types.
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public final StringRef userDialogMessage;
|
public final StringRef userDialogMessage;
|
||||||
@@ -245,6 +242,7 @@ public abstract class Setting<T> {
|
|||||||
*
|
*
|
||||||
* This method will be deleted in the future.
|
* This method will be deleted in the future.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
|
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
|
||||||
if (!oldPrefs.preferences.contains(settingKey)) {
|
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||||
return; // Nothing to do.
|
return; // Nothing to do.
|
||||||
@@ -289,6 +287,13 @@ public abstract class Setting<T> {
|
|||||||
*/
|
*/
|
||||||
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
|
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
|
||||||
setting.setValueFromString(newValue);
|
setting.setValueFromString(newValue);
|
||||||
|
|
||||||
|
// Clear the preference value since default is used, to allow changing
|
||||||
|
// the changing the default for a future release. Without this after upgrading
|
||||||
|
// the saved value will be whatever was the default when the app was first installed.
|
||||||
|
if (setting.isSetToDefault()) {
|
||||||
|
setting.removeFromPreferences();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -304,7 +309,33 @@ public abstract class Setting<T> {
|
|||||||
/**
|
/**
|
||||||
* Persistently saves the value.
|
* Persistently saves the value.
|
||||||
*/
|
*/
|
||||||
public abstract void save(@NonNull T newValue);
|
public final void save(@NonNull T newValue) {
|
||||||
|
if (value.equals(newValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
||||||
|
value = Objects.requireNonNull(newValue);
|
||||||
|
|
||||||
|
if (defaultValue.equals(newValue)) {
|
||||||
|
removeFromPreferences();
|
||||||
|
} else {
|
||||||
|
saveToPreferences();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save {@link #value} to {@link #preferences}.
|
||||||
|
*/
|
||||||
|
protected abstract void saveToPreferences();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove {@link #value} from {@link #preferences}.
|
||||||
|
*/
|
||||||
|
protected final void removeFromPreferences() {
|
||||||
|
Logger.printDebug(() -> "Clearing stored preference value (reset to default): " + key);
|
||||||
|
preferences.removeKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public abstract T get();
|
public abstract T get();
|
||||||
@@ -330,7 +361,7 @@ public abstract class Setting<T> {
|
|||||||
return value.equals(defaultValue);
|
return value.equals(defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return key + "=" + get();
|
return key + "=" + get();
|
||||||
@@ -420,6 +451,7 @@ public abstract class Setting<T> {
|
|||||||
|
|
||||||
boolean rebootSettingChanged = false;
|
boolean rebootSettingChanged = false;
|
||||||
int numberOfSettingsImported = 0;
|
int numberOfSettingsImported = 0;
|
||||||
|
//noinspection rawtypes
|
||||||
for (Setting setting : SETTINGS) {
|
for (Setting setting : SETTINGS) {
|
||||||
String key = setting.getImportExportKey();
|
String key = setting.getImportExportKey();
|
||||||
if (json.has(key)) {
|
if (json.has(key)) {
|
||||||
|
|||||||
@@ -55,10 +55,8 @@ public class StringSetting extends Setting<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void save(@NonNull String newValue) {
|
public void saveToPreferences() {
|
||||||
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
preferences.saveString(key, value);
|
||||||
value = Objects.requireNonNull(newValue);
|
|
||||||
preferences.saveString(key, newValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||||
try {
|
try {
|
||||||
Setting<?> setting = Setting.getSettingFromPath(str);
|
Setting<?> setting = Setting.getSettingFromPath(Objects.requireNonNull(str));
|
||||||
if (setting == null) {
|
if (setting == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -52,23 +52,21 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
}
|
}
|
||||||
Logger.printDebug(() -> "Preference changed: " + setting.key);
|
Logger.printDebug(() -> "Preference changed: " + setting.key);
|
||||||
|
|
||||||
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
if (!settingImportInProgress && !showingUserDialogMessage) {
|
||||||
updatePreference(pref, setting, true, settingImportInProgress);
|
if (setting.userDialogMessage != null && !prefIsSetToDefault(pref, setting)) {
|
||||||
// Update any other preference availability that may now be different.
|
// Do not change the setting yet, to allow preserving whatever
|
||||||
updateUIAvailability();
|
// list/text value was previously set if it needs to be reverted.
|
||||||
|
showSettingUserDialogConfirmation(pref, setting);
|
||||||
if (settingImportInProgress) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!showingUserDialogMessage) {
|
|
||||||
if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) {
|
|
||||||
showSettingUserDialogConfirmation((SwitchPreference) pref, (BooleanSetting) setting);
|
|
||||||
} else if (setting.rebootApp) {
|
} else if (setting.rebootApp) {
|
||||||
showRestartDialog(getContext());
|
showRestartDialog(getContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
||||||
|
updatePreference(pref, setting, true, settingImportInProgress);
|
||||||
|
// Update any other preference availability that may now be different.
|
||||||
|
updateUIAvailability();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||||
}
|
}
|
||||||
@@ -92,7 +90,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
Utils.setPreferenceTitlesToMultiLineIfNeeded(screen);
|
Utils.setPreferenceTitlesToMultiLineIfNeeded(screen);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) {
|
private void showSettingUserDialogConfirmation(Preference pref, Setting<?> setting) {
|
||||||
Utils.verifyOnMainThread();
|
Utils.verifyOnMainThread();
|
||||||
|
|
||||||
final var context = getContext();
|
final var context = getContext();
|
||||||
@@ -104,12 +102,19 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
.setTitle(confirmDialogTitle)
|
.setTitle(confirmDialogTitle)
|
||||||
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
|
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString())
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
||||||
|
// User confirmed, save to the Setting.
|
||||||
|
updatePreference(pref, setting, true, false);
|
||||||
|
|
||||||
|
// Update availability of other preferences that may be changed.
|
||||||
|
updateUIAvailability();
|
||||||
|
|
||||||
if (setting.rebootApp) {
|
if (setting.rebootApp) {
|
||||||
showRestartDialog(context);
|
showRestartDialog(context);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
||||||
switchPref.setChecked(setting.defaultValue); // Recursive call that resets the Setting value.
|
// Restore whatever the setting was before the change.
|
||||||
|
updatePreference(pref, setting, true, true);
|
||||||
})
|
})
|
||||||
.setOnDismissListener(dialog -> {
|
.setOnDismissListener(dialog -> {
|
||||||
showingUserDialogMessage = false;
|
showingUserDialogMessage = false;
|
||||||
@@ -132,6 +137,24 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
updatePreferenceScreen(getPreferenceScreen(), false, false);
|
updatePreferenceScreen(getPreferenceScreen(), false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If the preference is currently set to the default value of the Setting.
|
||||||
|
*/
|
||||||
|
protected boolean prefIsSetToDefault(Preference pref, Setting<?> setting) {
|
||||||
|
if (pref instanceof SwitchPreference switchPref) {
|
||||||
|
return switchPref.isChecked() == (Boolean) setting.defaultValue;
|
||||||
|
}
|
||||||
|
if (pref instanceof EditTextPreference editPreference) {
|
||||||
|
return editPreference.getText().equals(setting.defaultValue.toString());
|
||||||
|
}
|
||||||
|
if (pref instanceof ListPreference listPref) {
|
||||||
|
return listPref.getValue().equals(setting.defaultValue.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalStateException("Must override method to handle "
|
||||||
|
+ "preference type: " + pref.getClass());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs all UI Preferences to any {@link Setting} they represent.
|
* Syncs all UI Preferences to any {@link Setting} they represent.
|
||||||
*/
|
*/
|
||||||
@@ -170,23 +193,20 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
|||||||
protected void syncSettingWithPreference(@NonNull Preference pref,
|
protected void syncSettingWithPreference(@NonNull Preference pref,
|
||||||
@NonNull Setting<?> setting,
|
@NonNull Setting<?> setting,
|
||||||
boolean applySettingToPreference) {
|
boolean applySettingToPreference) {
|
||||||
if (pref instanceof SwitchPreference) {
|
if (pref instanceof SwitchPreference switchPref) {
|
||||||
SwitchPreference switchPref = (SwitchPreference) pref;
|
|
||||||
BooleanSetting boolSetting = (BooleanSetting) setting;
|
BooleanSetting boolSetting = (BooleanSetting) setting;
|
||||||
if (applySettingToPreference) {
|
if (applySettingToPreference) {
|
||||||
switchPref.setChecked(boolSetting.get());
|
switchPref.setChecked(boolSetting.get());
|
||||||
} else {
|
} else {
|
||||||
BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
|
BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
|
||||||
}
|
}
|
||||||
} else if (pref instanceof EditTextPreference) {
|
} else if (pref instanceof EditTextPreference editPreference) {
|
||||||
EditTextPreference editPreference = (EditTextPreference) pref;
|
|
||||||
if (applySettingToPreference) {
|
if (applySettingToPreference) {
|
||||||
editPreference.setText(setting.get().toString());
|
editPreference.setText(setting.get().toString());
|
||||||
} else {
|
} else {
|
||||||
Setting.privateSetValueFromString(setting, editPreference.getText());
|
Setting.privateSetValueFromString(setting, editPreference.getText());
|
||||||
}
|
}
|
||||||
} else if (pref instanceof ListPreference) {
|
} else if (pref instanceof ListPreference listPref) {
|
||||||
ListPreference listPref = (ListPreference) pref;
|
|
||||||
if (applySettingToPreference) {
|
if (applySettingToPreference) {
|
||||||
listPref.setValue(setting.get().toString());
|
listPref.setValue(setting.get().toString());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -54,9 +54,7 @@ public class ReVancedAboutPreference extends Preference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isDarkModeEnabled() {
|
protected boolean isDarkModeEnabled() {
|
||||||
Configuration config = getContext().getResources().getConfiguration();
|
return Utils.isDarkModeEnabled(getContext());
|
||||||
final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
|
||||||
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,67 +1,161 @@
|
|||||||
package app.revanced.extension.shared.spoof;
|
package app.revanced.extension.shared.spoof;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.spoof.DeviceHardwareSupport.allowAV1;
|
|
||||||
import static app.revanced.extension.shared.spoof.DeviceHardwareSupport.allowVP9;
|
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
|
||||||
public enum ClientType {
|
public enum ClientType {
|
||||||
// Specific purpose for age restricted, or private videos, because the iOS client is not logged in.
|
|
||||||
// https://dumps.tadiphone.dev/dumps/oculus/eureka
|
// https://dumps.tadiphone.dev/dumps/oculus/eureka
|
||||||
ANDROID_VR(28,
|
ANDROID_VR_NO_AUTH(
|
||||||
"Quest 3",
|
28,
|
||||||
"12",
|
|
||||||
"com.google.android.apps.youtube.vr.oculus/1.56.21 (Linux; U; Android 12; GB) gzip",
|
|
||||||
"32", // Android 12.1
|
|
||||||
"1.56.21",
|
|
||||||
"ANDROID_VR",
|
"ANDROID_VR",
|
||||||
true
|
"com.google.android.apps.youtube.vr.oculus",
|
||||||
|
"Oculus",
|
||||||
|
"Quest 3",
|
||||||
|
"Android",
|
||||||
|
"12",
|
||||||
|
// Android 12.1
|
||||||
|
"32",
|
||||||
|
"SQ3A.220605.009.A1",
|
||||||
|
"132.0.6808.3",
|
||||||
|
"1.61.48",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"Android VR No auth"
|
||||||
),
|
),
|
||||||
// Specific for kids videos.
|
// Chromecast with Google TV 4K.
|
||||||
IOS(5,
|
// https://dumps.tadiphone.dev/dumps/google/kirkwood
|
||||||
// iPhone 15 supports AV1 hardware decoding.
|
ANDROID_UNPLUGGED(
|
||||||
// Only use if this Android device also has hardware decoding.
|
29,
|
||||||
allowAV1()
|
"ANDROID_UNPLUGGED",
|
||||||
? "iPhone16,2" // 15 Pro Max
|
"com.google.android.apps.youtube.unplugged",
|
||||||
: "iPhone11,4", // XS Max
|
"Google",
|
||||||
// iOS 14+ forces VP9.
|
"Google TV Streamer",
|
||||||
allowVP9()
|
"Android",
|
||||||
? "17.5.1.21F90"
|
"14",
|
||||||
: "13.7.17H35",
|
"34",
|
||||||
allowVP9()
|
"UTT3.240625.001.K5",
|
||||||
? "com.google.ios.youtube/19.47.7 (iPhone; U; CPU iOS 17_5_1 like Mac OS X)"
|
"132.0.6808.3",
|
||||||
: "com.google.ios.youtube/19.47.7 (iPhone; U; CPU iOS 13_7 like Mac OS X)",
|
"8.49.0",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"Android TV"
|
||||||
|
),
|
||||||
|
// Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
|
||||||
|
// Google Pixel 9 Pro Fold
|
||||||
|
// https://dumps.tadiphone.dev/dumps/google/barbet
|
||||||
|
ANDROID_CREATOR(
|
||||||
|
14,
|
||||||
|
"ANDROID_CREATOR",
|
||||||
|
"com.google.android.apps.youtube.creator",
|
||||||
|
"Google",
|
||||||
|
"Pixel 9 Pro Fold",
|
||||||
|
"Android",
|
||||||
|
"15",
|
||||||
|
"35",
|
||||||
|
"AP3A.241005.015.A2",
|
||||||
|
"132.0.6779.0",
|
||||||
|
"23.47.101",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"Android Creator"
|
||||||
|
),
|
||||||
|
IOS_UNPLUGGED(
|
||||||
|
33,
|
||||||
|
"IOS_UNPLUGGED",
|
||||||
|
"com.google.ios.youtubeunplugged",
|
||||||
|
"Apple",
|
||||||
|
forceAVC()
|
||||||
|
// 11 Pro Max (last device with iOS 13)
|
||||||
|
? "iPhone12,5"
|
||||||
|
// 15 Pro Max
|
||||||
|
: "iPhone16,2",
|
||||||
|
"iOS",
|
||||||
|
forceAVC()
|
||||||
|
// iOS 13 and earlier uses only AVC. 14+ adds VP9 and AV1.
|
||||||
|
? "13.7.17H35"
|
||||||
|
: "18.2.22C152",
|
||||||
|
null,
|
||||||
|
null,
|
||||||
null,
|
null,
|
||||||
// Version number should be a valid iOS release.
|
// Version number should be a valid iOS release.
|
||||||
// https://www.ipa4fun.com/history/185230
|
// https://www.ipa4fun.com/history/152043/
|
||||||
"19.47.7",
|
forceAVC()
|
||||||
"IOS",
|
// Some newer versions can also force AVC,
|
||||||
false
|
// but 6.45 is the last version that supports iOS 13.
|
||||||
|
? "6.45"
|
||||||
|
: "8.49",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
forceAVC()
|
||||||
|
? "iOS TV Force AVC"
|
||||||
|
: "iOS TV"
|
||||||
|
),
|
||||||
|
ANDROID_VR_AUTH(
|
||||||
|
ANDROID_VR_NO_AUTH.id,
|
||||||
|
ANDROID_VR_NO_AUTH.clientName,
|
||||||
|
ANDROID_VR_NO_AUTH.packageName,
|
||||||
|
ANDROID_VR_NO_AUTH.deviceMake,
|
||||||
|
ANDROID_VR_NO_AUTH.deviceModel,
|
||||||
|
ANDROID_VR_NO_AUTH.osName,
|
||||||
|
ANDROID_VR_NO_AUTH.osVersion,
|
||||||
|
ANDROID_VR_NO_AUTH.androidSdkVersion,
|
||||||
|
ANDROID_VR_NO_AUTH.buildId,
|
||||||
|
ANDROID_VR_NO_AUTH.cronetVersion,
|
||||||
|
ANDROID_VR_NO_AUTH.clientVersion,
|
||||||
|
ANDROID_VR_NO_AUTH.requiresAuth,
|
||||||
|
true,
|
||||||
|
"Android VR Auth"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
private static boolean forceAVC() {
|
||||||
|
return BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* YouTube
|
* YouTube
|
||||||
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
|
* <a href="https://github.com/zerodytrash/YouTube-Internal-Clients?tab=readme-ov-file#clients">client type</a>
|
||||||
*/
|
*/
|
||||||
public final int id;
|
public final int id;
|
||||||
|
|
||||||
/**
|
public final String clientName;
|
||||||
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model)
|
|
||||||
*/
|
|
||||||
public final String deviceModel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Device OS version.
|
* App package name.
|
||||||
*/
|
*/
|
||||||
public final String osVersion;
|
private final String packageName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Player user-agent.
|
* Player user-agent.
|
||||||
*/
|
*/
|
||||||
public final String userAgent;
|
public final String userAgent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device model, equivalent to {@link Build#MANUFACTURER} (System property: ro.product.vendor.manufacturer)
|
||||||
|
*/
|
||||||
|
public final String deviceMake;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device model, equivalent to {@link Build#MODEL} (System property: ro.product.vendor.model)
|
||||||
|
*/
|
||||||
|
public final String deviceModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device OS name.
|
||||||
|
*/
|
||||||
|
public final String osName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device OS version.
|
||||||
|
*/
|
||||||
|
public final String osVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Android SDK version, equivalent to {@link Build.VERSION#SDK} (System property: ro.build.version.sdk)
|
* Android SDK version, equivalent to {@link Build.VERSION#SDK} (System property: ro.build.version.sdk)
|
||||||
* Field is null if not applicable.
|
* Field is null if not applicable.
|
||||||
@@ -70,9 +164,18 @@ public enum ClientType {
|
|||||||
public final String androidSdkVersion;
|
public final String androidSdkVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Client name.
|
* Android build id, equivalent to {@link Build#ID}.
|
||||||
|
* Field is null if not applicable.
|
||||||
*/
|
*/
|
||||||
public final String clientName;
|
@Nullable
|
||||||
|
private final String buildId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cronet release version, as found in decompiled client apk.
|
||||||
|
* Field is null if not applicable.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private final String cronetVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App version.
|
* App version.
|
||||||
@@ -80,26 +183,77 @@ public enum ClientType {
|
|||||||
public final String clientVersion;
|
public final String clientVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the client can access the API logged in.
|
* If this client requires authentication and does not work
|
||||||
|
* if logged out or in incognito mode.
|
||||||
*/
|
*/
|
||||||
public final boolean canLogin;
|
public final boolean requiresAuth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the client should use authentication if available.
|
||||||
|
*/
|
||||||
|
public final boolean useAuth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Friendly name displayed in stats for nerds.
|
||||||
|
*/
|
||||||
|
public final String friendlyName;
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantLocale")
|
||||||
ClientType(int id,
|
ClientType(int id,
|
||||||
String deviceModel,
|
|
||||||
String osVersion,
|
|
||||||
String userAgent,
|
|
||||||
@Nullable String androidSdkVersion,
|
|
||||||
String clientVersion,
|
|
||||||
String clientName,
|
String clientName,
|
||||||
boolean canLogin
|
String packageName,
|
||||||
) {
|
String deviceMake,
|
||||||
|
String deviceModel,
|
||||||
|
String osName,
|
||||||
|
String osVersion,
|
||||||
|
@Nullable String androidSdkVersion,
|
||||||
|
@Nullable String buildId,
|
||||||
|
@Nullable String cronetVersion,
|
||||||
|
String clientVersion,
|
||||||
|
boolean requiresAuth,
|
||||||
|
boolean useAuth,
|
||||||
|
String friendlyName) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.deviceModel = deviceModel;
|
|
||||||
this.osVersion = osVersion;
|
|
||||||
this.userAgent = userAgent;
|
|
||||||
this.androidSdkVersion = androidSdkVersion;
|
|
||||||
this.clientVersion = clientVersion;
|
|
||||||
this.clientName = clientName;
|
this.clientName = clientName;
|
||||||
this.canLogin = canLogin;
|
this.packageName = packageName;
|
||||||
|
this.deviceMake = deviceMake;
|
||||||
|
this.deviceModel = deviceModel;
|
||||||
|
this.osName = osName;
|
||||||
|
this.osVersion = osVersion;
|
||||||
|
this.androidSdkVersion = androidSdkVersion;
|
||||||
|
this.buildId = buildId;
|
||||||
|
this.cronetVersion = cronetVersion;
|
||||||
|
this.clientVersion = clientVersion;
|
||||||
|
this.requiresAuth = requiresAuth;
|
||||||
|
this.useAuth = useAuth;
|
||||||
|
this.friendlyName = friendlyName;
|
||||||
|
|
||||||
|
Locale defaultLocale = Locale.getDefault();
|
||||||
|
if (androidSdkVersion == null) {
|
||||||
|
// Convert version from '18.2.22C152' into '18_2_22'
|
||||||
|
String userAgentOsVersion = osVersion
|
||||||
|
.replaceAll("(\\d+\\.\\d+\\.\\d+).*", "$1")
|
||||||
|
.replace(".", "_");
|
||||||
|
// https://github.com/mitmproxy/mitmproxy/issues/4836
|
||||||
|
this.userAgent = String.format("%s/%s (%s; U; CPU iOS %s like Mac OS X; %s)",
|
||||||
|
packageName,
|
||||||
|
clientVersion,
|
||||||
|
deviceModel,
|
||||||
|
userAgentOsVersion,
|
||||||
|
defaultLocale
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.userAgent = String.format("%s/%s (Linux; U; Android %s; %s; %s; Build/%s; Cronet/%s)",
|
||||||
|
packageName,
|
||||||
|
clientVersion,
|
||||||
|
osVersion,
|
||||||
|
defaultLocale,
|
||||||
|
deviceModel,
|
||||||
|
Objects.requireNonNull(buildId),
|
||||||
|
Objects.requireNonNull(cronetVersion)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Logger.printDebug(() -> "userAgent: " + this.userAgent);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
package app.revanced.extension.shared.spoof;
|
|
||||||
|
|
||||||
import android.media.MediaCodecInfo;
|
|
||||||
import android.media.MediaCodecList;
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
|
||||||
|
|
||||||
public class DeviceHardwareSupport {
|
|
||||||
public static final boolean DEVICE_HAS_HARDWARE_DECODING_VP9;
|
|
||||||
public static final boolean DEVICE_HAS_HARDWARE_DECODING_AV1;
|
|
||||||
|
|
||||||
static {
|
|
||||||
boolean vp9found = false;
|
|
||||||
boolean av1found = false;
|
|
||||||
MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS);
|
|
||||||
final boolean deviceIsAndroidTenOrLater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q;
|
|
||||||
|
|
||||||
for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) {
|
|
||||||
final boolean isHardwareAccelerated = deviceIsAndroidTenOrLater
|
|
||||||
? codecInfo.isHardwareAccelerated()
|
|
||||||
: !codecInfo.getName().startsWith("OMX.google"); // Software decoder.
|
|
||||||
if (isHardwareAccelerated && !codecInfo.isEncoder()) {
|
|
||||||
for (String type : codecInfo.getSupportedTypes()) {
|
|
||||||
if (type.equalsIgnoreCase("video/x-vnd.on2.vp9")) {
|
|
||||||
vp9found = true;
|
|
||||||
} else if (type.equalsIgnoreCase("video/av01")) {
|
|
||||||
av1found = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DEVICE_HAS_HARDWARE_DECODING_VP9 = vp9found;
|
|
||||||
DEVICE_HAS_HARDWARE_DECODING_AV1 = av1found;
|
|
||||||
|
|
||||||
Logger.printDebug(() -> DEVICE_HAS_HARDWARE_DECODING_AV1
|
|
||||||
? "Device supports AV1 hardware decoding\n"
|
|
||||||
: "Device does not support AV1 hardware decoding\n"
|
|
||||||
+ (DEVICE_HAS_HARDWARE_DECODING_VP9
|
|
||||||
? "Device supports VP9 hardware decoding"
|
|
||||||
: "Device does not support VP9 hardware decoding"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean allowVP9() {
|
|
||||||
return DEVICE_HAS_HARDWARE_DECODING_VP9 && !BaseSettings.SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean allowAV1() {
|
|
||||||
return allowVP9() && DEVICE_HAS_HARDWARE_DECODING_AV1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,29 +1,45 @@
|
|||||||
package app.revanced.extension.shared.spoof;
|
package app.revanced.extension.shared.spoof;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.Setting;
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class SpoofVideoStreamsPatch {
|
public class SpoofVideoStreamsPatch {
|
||||||
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
private static final boolean SPOOF_STREAMING_DATA = BaseSettings.SPOOF_VIDEO_STREAMS.get();
|
||||||
|
|
||||||
|
private static final boolean FIX_HLS_CURRENT_TIME = SPOOF_STREAMING_DATA
|
||||||
|
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any unreachable ip address. Used to intentionally fail requests.
|
* Any unreachable ip address. Used to intentionally fail requests.
|
||||||
*/
|
*/
|
||||||
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
|
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
|
||||||
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
|
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return If this patch was included during patching.
|
||||||
|
*/
|
||||||
|
private static boolean isPatchIncluded() {
|
||||||
|
return false; // Modified during patching.
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean notSpoofingToAndroid() {
|
||||||
|
return !isPatchIncluded()
|
||||||
|
|| !BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
||||||
|
|| BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* Blocks /get_watch requests by returning an unreachable URI.
|
* Blocks /get_watch requests by returning an unreachable URI.
|
||||||
@@ -61,9 +77,9 @@ public class SpoofVideoStreamsPatch {
|
|||||||
String path = originalUri.getPath();
|
String path = originalUri.getPath();
|
||||||
|
|
||||||
if (path != null && path.contains("initplayback")) {
|
if (path != null && path.contains("initplayback")) {
|
||||||
Logger.printDebug(() -> "Blocking 'initplayback' by returning unreachable url");
|
Logger.printDebug(() -> "Blocking 'initplayback' by clearing query");
|
||||||
|
|
||||||
return UNREACHABLE_HOST_URI_STRING;
|
return originalUri.buildUpon().clearQuery().build().toString();
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
|
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
|
||||||
@@ -80,6 +96,17 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return SPOOF_STREAMING_DATA;
|
return SPOOF_STREAMING_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Only invoked when playing a livestream on an iOS client.
|
||||||
|
*/
|
||||||
|
public static boolean fixHLSCurrentTime(boolean original) {
|
||||||
|
if (!SPOOF_STREAMING_DATA) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@@ -88,11 +115,27 @@ public class SpoofVideoStreamsPatch {
|
|||||||
try {
|
try {
|
||||||
Uri uri = Uri.parse(url);
|
Uri uri = Uri.parse(url);
|
||||||
String path = uri.getPath();
|
String path = uri.getPath();
|
||||||
// 'heartbeat' has no video id and appears to be only after playback has started.
|
if (path == null || !path.contains("player")) {
|
||||||
if (path != null && path.contains("player") && !path.contains("heartbeat")) {
|
return;
|
||||||
String videoId = Objects.requireNonNull(uri.getQueryParameter("id"));
|
|
||||||
StreamingDataRequest.fetchRequest(videoId, requestHeaders);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 'get_drm_license' has no video id and appears to happen when waiting for a paid video to start.
|
||||||
|
// 'heartbeat' has no video id and appears to be only after playback has started.
|
||||||
|
// 'refresh' has no video id and appears to happen when waiting for a livestream to start.
|
||||||
|
// 'ad_break' has no video id.
|
||||||
|
if (path.contains("get_drm_license") || path.contains("heartbeat")
|
||||||
|
|| path.contains("refresh") || path.contains("ad_break")) {
|
||||||
|
Logger.printDebug(() -> "Ignoring path: " + path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String id = uri.getQueryParameter("id");
|
||||||
|
if (id == null) {
|
||||||
|
Logger.printException(() -> "Ignoring request with no id: " + url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamingDataRequest.fetchRequest(id, requestHeaders);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "buildRequest failure", ex);
|
Logger.printException(() -> "buildRequest failure", ex);
|
||||||
}
|
}
|
||||||
@@ -158,10 +201,37 @@ public class SpoofVideoStreamsPatch {
|
|||||||
return postData;
|
return postData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class ForceiOSAVCAvailability implements Setting.Availability {
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static String appendSpoofedClient(String videoFormat) {
|
||||||
|
try {
|
||||||
|
if (SPOOF_STREAMING_DATA && BaseSettings.SPOOF_STREAMING_DATA_STATS_FOR_NERDS.get()
|
||||||
|
&& !TextUtils.isEmpty(videoFormat)) {
|
||||||
|
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages.
|
||||||
|
return "\u202D" + videoFormat + "\u2009(" // u202D = left to right override
|
||||||
|
+ StreamingDataRequest.getLastSpoofedClientName() + ")";
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "appendSpoofedClient failure", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return videoFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
return BaseSettings.SPOOF_VIDEO_STREAMS.get() && BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS;
|
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
||||||
|
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_NO_AUTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class SpoofiOSAvailability implements Setting.Availability {
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return BaseSettings.SPOOF_VIDEO_STREAMS.get()
|
||||||
|
&& BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.IOS_UNPLUGGED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import org.json.JSONObject;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
|
||||||
import app.revanced.extension.shared.requests.Requester;
|
import app.revanced.extension.shared.requests.Requester;
|
||||||
import app.revanced.extension.shared.requests.Route;
|
import app.revanced.extension.shared.requests.Route;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.spoof.ClientType;
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
final class PlayerRoutes {
|
final class PlayerRoutes {
|
||||||
@@ -19,40 +20,50 @@ final class PlayerRoutes {
|
|||||||
"?fields=streamingData" +
|
"?fields=streamingData" +
|
||||||
"&alt=proto"
|
"&alt=proto"
|
||||||
).compile();
|
).compile();
|
||||||
|
|
||||||
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
private static final String YT_API_URL = "https://youtubei.googleapis.com/youtubei/v1/";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TCP connection and HTTP read timeout
|
* TCP connection and HTTP read timeout
|
||||||
*/
|
*/
|
||||||
private static final int CONNECTION_TIMEOUT_MILLISECONDS = 10 * 1000; // 10 Seconds.
|
private static final int CONNECTION_TIMEOUT_MILLISECONDS = 10 * 1000; // 10 Seconds.
|
||||||
|
|
||||||
private static final String LOCALE_LANGUAGE = Utils.getContext().getResources()
|
|
||||||
.getConfiguration().locale.getLanguage();
|
|
||||||
|
|
||||||
private PlayerRoutes() {
|
private PlayerRoutes() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static String createInnertubeBody(ClientType clientType) {
|
static String createInnertubeBody(ClientType clientType, String videoId) {
|
||||||
JSONObject innerTubeBody = new JSONObject();
|
JSONObject innerTubeBody = new JSONObject();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JSONObject context = new JSONObject();
|
JSONObject context = new JSONObject();
|
||||||
|
|
||||||
|
// Can override default language only if no login is used.
|
||||||
|
// Could use preferred audio for all clients that do not login,
|
||||||
|
// but if this is a fall over client it will set the language even though
|
||||||
|
// the audio language is not selectable in the UI.
|
||||||
|
ClientType userSelectedClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
|
||||||
|
Locale streamLocale = userSelectedClient == ClientType.ANDROID_VR_NO_AUTH
|
||||||
|
? BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get().getLocale()
|
||||||
|
: Locale.getDefault();
|
||||||
|
|
||||||
JSONObject client = new JSONObject();
|
JSONObject client = new JSONObject();
|
||||||
// Required to use correct default audio channel with iOS.
|
client.put("deviceMake", clientType.deviceMake);
|
||||||
client.put("hl", LOCALE_LANGUAGE);
|
|
||||||
client.put("clientName", clientType.name());
|
|
||||||
client.put("clientVersion", clientType.clientVersion);
|
|
||||||
client.put("deviceModel", clientType.deviceModel);
|
client.put("deviceModel", clientType.deviceModel);
|
||||||
|
client.put("clientName", clientType.clientName);
|
||||||
|
client.put("clientVersion", clientType.clientVersion);
|
||||||
|
client.put("osName", clientType.osName);
|
||||||
client.put("osVersion", clientType.osVersion);
|
client.put("osVersion", clientType.osVersion);
|
||||||
if (clientType.androidSdkVersion != null) {
|
if (clientType.androidSdkVersion != null) {
|
||||||
client.put("androidSdkVersion", clientType.androidSdkVersion);
|
client.put("androidSdkVersion", clientType.androidSdkVersion);
|
||||||
}
|
}
|
||||||
|
client.put("hl", streamLocale.getLanguage());
|
||||||
|
client.put("gl", streamLocale.getCountry());
|
||||||
context.put("client", client);
|
context.put("client", client);
|
||||||
|
|
||||||
innerTubeBody.put("context", context);
|
innerTubeBody.put("context", context);
|
||||||
innerTubeBody.put("contentCheckOk", true);
|
innerTubeBody.put("contentCheckOk", true);
|
||||||
innerTubeBody.put("racyCheckOk", true);
|
innerTubeBody.put("racyCheckOk", true);
|
||||||
innerTubeBody.put("videoId", "%s");
|
innerTubeBody.put("videoId", videoId);
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Logger.printException(() -> "Failed to create innerTubeBody", e);
|
Logger.printException(() -> "Failed to create innerTubeBody", e);
|
||||||
}
|
}
|
||||||
@@ -68,6 +79,9 @@ final class PlayerRoutes {
|
|||||||
|
|
||||||
connection.setRequestProperty("Content-Type", "application/json");
|
connection.setRequestProperty("Content-Type", "application/json");
|
||||||
connection.setRequestProperty("User-Agent", clientType.userAgent);
|
connection.setRequestProperty("User-Agent", clientType.userAgent);
|
||||||
|
// Not a typo. "Client-Name" uses the client type id.
|
||||||
|
connection.setRequestProperty("X-YouTube-Client-Name", String.valueOf(clientType.id));
|
||||||
|
connection.setRequestProperty("X-YouTube-Client-Version", clientType.clientVersion);
|
||||||
|
|
||||||
connection.setUseCaches(false);
|
connection.setUseCaches(false);
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
|
|||||||
@@ -36,20 +36,40 @@ import app.revanced.extension.shared.spoof.ClientType;
|
|||||||
public class StreamingDataRequest {
|
public class StreamingDataRequest {
|
||||||
|
|
||||||
private static final ClientType[] CLIENT_ORDER_TO_USE;
|
private static final ClientType[] CLIENT_ORDER_TO_USE;
|
||||||
|
|
||||||
|
static {
|
||||||
|
ClientType[] allClientTypes = ClientType.values();
|
||||||
|
ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
|
||||||
|
|
||||||
|
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
|
||||||
|
CLIENT_ORDER_TO_USE[0] = preferredClient;
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
|
for (ClientType c : allClientTypes) {
|
||||||
|
if (c != preferredClient) {
|
||||||
|
CLIENT_ORDER_TO_USE[i++] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static final String AUTHORIZATION_HEADER = "Authorization";
|
private static final String AUTHORIZATION_HEADER = "Authorization";
|
||||||
|
|
||||||
private static final String[] REQUEST_HEADER_KEYS = {
|
private static final String[] REQUEST_HEADER_KEYS = {
|
||||||
AUTHORIZATION_HEADER, // Available only to logged-in users.
|
AUTHORIZATION_HEADER, // Available only to logged-in users.
|
||||||
"X-GOOG-API-FORMAT-VERSION",
|
"X-GOOG-API-FORMAT-VERSION",
|
||||||
"X-Goog-Visitor-Id"
|
"X-Goog-Visitor-Id"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TCP connection and HTTP read timeout.
|
* TCP connection and HTTP read timeout.
|
||||||
*/
|
*/
|
||||||
private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000;
|
private static final int HTTP_TIMEOUT_MILLISECONDS = 10 * 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Any arbitrarily large value, but must be at least twice {@link #HTTP_TIMEOUT_MILLISECONDS}
|
* Any arbitrarily large value, but must be at least twice {@link #HTTP_TIMEOUT_MILLISECONDS}
|
||||||
*/
|
*/
|
||||||
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
|
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
|
||||||
|
|
||||||
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
|
||||||
new LinkedHashMap<>(100) {
|
new LinkedHashMap<>(100) {
|
||||||
/**
|
/**
|
||||||
@@ -67,22 +87,15 @@ public class StreamingDataRequest {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
static {
|
private static volatile ClientType lastSpoofedClientType;
|
||||||
ClientType[] allClientTypes = ClientType.values();
|
|
||||||
ClientType preferredClient = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
|
|
||||||
|
|
||||||
CLIENT_ORDER_TO_USE = new ClientType[allClientTypes.length];
|
public static String getLastSpoofedClientName() {
|
||||||
CLIENT_ORDER_TO_USE[0] = preferredClient;
|
ClientType client = lastSpoofedClientType;
|
||||||
|
return client == null ? "Unknown" : client.friendlyName;
|
||||||
int i = 1;
|
|
||||||
for (ClientType c : allClientTypes) {
|
|
||||||
if (c != preferredClient) {
|
|
||||||
CLIENT_ORDER_TO_USE[i++] = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String videoId;
|
private final String videoId;
|
||||||
|
|
||||||
private final Future<ByteBuffer> future;
|
private final Future<ByteBuffer> future;
|
||||||
|
|
||||||
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
|
private StreamingDataRequest(String videoId, Map<String, String> playerHeaders) {
|
||||||
@@ -107,7 +120,8 @@ public class StreamingDataRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static HttpURLConnection send(ClientType clientType, String videoId,
|
private static HttpURLConnection send(ClientType clientType,
|
||||||
|
String videoId,
|
||||||
Map<String, String> playerHeaders,
|
Map<String, String> playerHeaders,
|
||||||
boolean showErrorToasts) {
|
boolean showErrorToasts) {
|
||||||
Objects.requireNonNull(clientType);
|
Objects.requireNonNull(clientType);
|
||||||
@@ -115,26 +129,40 @@ public class StreamingDataRequest {
|
|||||||
Objects.requireNonNull(playerHeaders);
|
Objects.requireNonNull(playerHeaders);
|
||||||
|
|
||||||
final long startTime = System.currentTimeMillis();
|
final long startTime = System.currentTimeMillis();
|
||||||
String clientTypeName = clientType.name();
|
|
||||||
Logger.printDebug(() -> "Fetching video streams for: " + videoId + " using client: " + clientType.name());
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType);
|
HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType);
|
||||||
connection.setConnectTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
connection.setConnectTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
||||||
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
connection.setReadTimeout(HTTP_TIMEOUT_MILLISECONDS);
|
||||||
|
|
||||||
for (String key : REQUEST_HEADER_KEYS) {
|
boolean authHeadersIncludes = false;
|
||||||
if (!clientType.canLogin && key.equals(AUTHORIZATION_HEADER)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (String key : REQUEST_HEADER_KEYS) {
|
||||||
String value = playerHeaders.get(key);
|
String value = playerHeaders.get(key);
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
|
if (key.equals(AUTHORIZATION_HEADER)) {
|
||||||
|
if (!clientType.useAuth) {
|
||||||
|
Logger.printDebug(() -> "Not including request header: " + key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
authHeadersIncludes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Including request header: " + key);
|
||||||
connection.setRequestProperty(key, value);
|
connection.setRequestProperty(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String innerTubeBody = String.format(PlayerRoutes.createInnertubeBody(clientType), videoId);
|
if (!authHeadersIncludes && clientType.requiresAuth) {
|
||||||
|
Logger.printDebug(() -> "Skipping client since user is not logged in: " + clientType
|
||||||
|
+ " videoId: " + videoId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Fetching video streams for: " + videoId + " using client: " + clientType);
|
||||||
|
|
||||||
|
String innerTubeBody = PlayerRoutes.createInnertubeBody(clientType, videoId);
|
||||||
byte[] requestBody = innerTubeBody.getBytes(StandardCharsets.UTF_8);
|
byte[] requestBody = innerTubeBody.getBytes(StandardCharsets.UTF_8);
|
||||||
connection.setFixedLengthStreamingMode(requestBody.length);
|
connection.setFixedLengthStreamingMode(requestBody.length);
|
||||||
connection.getOutputStream().write(requestBody);
|
connection.getOutputStream().write(requestBody);
|
||||||
@@ -142,8 +170,10 @@ public class StreamingDataRequest {
|
|||||||
final int responseCode = connection.getResponseCode();
|
final int responseCode = connection.getResponseCode();
|
||||||
if (responseCode == 200) return connection;
|
if (responseCode == 200) return connection;
|
||||||
|
|
||||||
handleConnectionError(clientTypeName + " not available with response code: "
|
// This situation likely means the patches are outdated.
|
||||||
+ responseCode + " message: " + connection.getResponseMessage(),
|
// Use a toast message that suggests updating.
|
||||||
|
handleConnectionError("Playback error (App is outdated?) " + clientType + ": "
|
||||||
|
+ responseCode + " response: " + connection.getResponseMessage(),
|
||||||
null, showErrorToasts);
|
null, showErrorToasts);
|
||||||
} catch (SocketTimeoutException ex) {
|
} catch (SocketTimeoutException ex) {
|
||||||
handleConnectionError("Connection timeout", ex, showErrorToasts);
|
handleConnectionError("Connection timeout", ex, showErrorToasts);
|
||||||
@@ -164,7 +194,7 @@ public class StreamingDataRequest {
|
|||||||
// Retry with different client if empty response body is received.
|
// Retry with different client if empty response body is received.
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (ClientType clientType : CLIENT_ORDER_TO_USE) {
|
for (ClientType clientType : CLIENT_ORDER_TO_USE) {
|
||||||
// Show an error if the last client type fails, or if the debug is enabled then show for all attempts.
|
// Show an error if the last client type fails, or if debug is enabled then show for all attempts.
|
||||||
final boolean showErrorToast = (++i == CLIENT_ORDER_TO_USE.length) || debugEnabled;
|
final boolean showErrorToast = (++i == CLIENT_ORDER_TO_USE.length) || debugEnabled;
|
||||||
|
|
||||||
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
HttpURLConnection connection = send(clientType, videoId, playerHeaders, showErrorToast);
|
||||||
@@ -172,17 +202,22 @@ public class StreamingDataRequest {
|
|||||||
try {
|
try {
|
||||||
// gzip encoding doesn't response with content length (-1),
|
// gzip encoding doesn't response with content length (-1),
|
||||||
// but empty response body does.
|
// but empty response body does.
|
||||||
if (connection.getContentLength() != 0) {
|
if (connection.getContentLength() == 0) {
|
||||||
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream())) {
|
if (BaseSettings.DEBUG.get() && BaseSettings.DEBUG_TOAST_ON_ERROR.get()) {
|
||||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
Utils.showToastShort("Ignoring empty spoof stream client: " + clientType);
|
||||||
byte[] buffer = new byte[2048];
|
}
|
||||||
int bytesRead;
|
} else {
|
||||||
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
try (InputStream inputStream = new BufferedInputStream(connection.getInputStream());
|
||||||
baos.write(buffer, 0, bytesRead);
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||||
}
|
|
||||||
|
|
||||||
return ByteBuffer.wrap(baos.toByteArray());
|
byte[] buffer = new byte[2048];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) >= 0) {
|
||||||
|
baos.write(buffer, 0, bytesRead);
|
||||||
}
|
}
|
||||||
|
lastSpoofedClientType = clientType;
|
||||||
|
|
||||||
|
return ByteBuffer.wrap(baos.toByteArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
@@ -191,7 +226,8 @@ public class StreamingDataRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleConnectionError("Could not fetch any client streams", null, debugEnabled);
|
lastSpoofedClientType = null;
|
||||||
|
handleConnectionError("Could not fetch any client streams", null, true);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.revanced.extension"
|
namespace = "app.revanced.extension"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
|
|||||||
@@ -3,3 +3,14 @@ dependencies {
|
|||||||
compileOnly(project(":extensions:tiktok:stub"))
|
compileOnly(project(":extensions:tiktok:stub"))
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
package app.revanced.extension.tiktok;
|
package app.revanced.extension.tiktok;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.Utils.isDarkModeEnabled;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.ColorInt;
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.StringSetting;
|
import app.revanced.extension.shared.settings.StringSetting;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
||||||
|
private static final long[] DEFAULT_MIN_MAX_VALUES = {0L, Long.MAX_VALUE};
|
||||||
|
|
||||||
// Edit: This could be handled using a custom Setting<Long[]> class
|
// Edit: This could be handled using a custom Setting<Long[]> class
|
||||||
// that saves its value to preferences and JSON using the formatted String created here.
|
// that saves its value to preferences and JSON using the formatted String created here.
|
||||||
public static long[] parseMinMax(StringSetting setting) {
|
public static long[] parseMinMax(StringSetting setting) {
|
||||||
@@ -20,6 +31,29 @@ public class Utils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setting.save("0-" + Long.MAX_VALUE);
|
setting.save("0-" + Long.MAX_VALUE);
|
||||||
return new long[]{0L, Long.MAX_VALUE};
|
return DEFAULT_MIN_MAX_VALUES;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Colors picked by hand. These should be replaced with the styled resources TikTok uses.
|
||||||
|
private static final @ColorInt int TEXT_DARK_MODE_TITLE = Color.WHITE;
|
||||||
|
private static final @ColorInt int TEXT_DARK_MODE_SUMMARY
|
||||||
|
= Color.argb(255, 170, 170, 170);
|
||||||
|
|
||||||
|
private static final @ColorInt int TEXT_LIGHT_MODE_TITLE = Color.BLACK;
|
||||||
|
private static final @ColorInt int TEXT_LIGHT_MODE_SUMMARY
|
||||||
|
= Color.argb(255, 80, 80, 80);
|
||||||
|
|
||||||
|
public static void setTitleAndSummaryColor(Context context, View view) {
|
||||||
|
final boolean darkModeEnabled = isDarkModeEnabled(context);
|
||||||
|
|
||||||
|
TextView title = view.findViewById(android.R.id.title);
|
||||||
|
title.setTextColor(darkModeEnabled
|
||||||
|
? TEXT_DARK_MODE_TITLE
|
||||||
|
: TEXT_LIGHT_MODE_TITLE);
|
||||||
|
|
||||||
|
TextView summary = view.findViewById(android.R.id.summary);
|
||||||
|
summary.setTextColor(darkModeEnabled
|
||||||
|
? TEXT_DARK_MODE_SUMMARY
|
||||||
|
: TEXT_LIGHT_MODE_SUMMARY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
package app.revanced.extension.tiktok.feedfilter;
|
package app.revanced.extension.tiktok.feedfilter;
|
||||||
|
|
||||||
import app.revanced.extension.tiktok.settings.Settings;
|
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
||||||
|
|
||||||
import static app.revanced.extension.tiktok.Utils.parseMinMax;
|
import app.revanced.extension.tiktok.Utils;
|
||||||
|
import app.revanced.extension.tiktok.settings.Settings;
|
||||||
|
|
||||||
public final class LikeCountFilter implements IFilter {
|
public final class LikeCountFilter implements IFilter {
|
||||||
|
|
||||||
final long minLike;
|
final long minLike;
|
||||||
final long maxLike;
|
final long maxLike;
|
||||||
|
|
||||||
LikeCountFilter() {
|
LikeCountFilter() {
|
||||||
long[] minMax = parseMinMax(Settings.MIN_MAX_LIKES);
|
long[] minMax = Utils.parseMinMax(Settings.MIN_MAX_LIKES);
|
||||||
minLike = minMax[0];
|
minLike = minMax[0];
|
||||||
maxLike = minMax[1];
|
maxLike = minMax[1];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package app.revanced.extension.tiktok.feedfilter;
|
package app.revanced.extension.tiktok.feedfilter;
|
||||||
|
|
||||||
|
import app.revanced.extension.tiktok.Utils;
|
||||||
import app.revanced.extension.tiktok.settings.Settings;
|
import app.revanced.extension.tiktok.settings.Settings;
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
||||||
|
|
||||||
import static app.revanced.extension.tiktok.Utils.parseMinMax;
|
|
||||||
|
|
||||||
public class ViewCountFilter implements IFilter {
|
public class ViewCountFilter implements IFilter {
|
||||||
final long minView;
|
final long minView;
|
||||||
final long maxView;
|
final long maxView;
|
||||||
|
|
||||||
ViewCountFilter() {
|
ViewCountFilter() {
|
||||||
long[] minMax = parseMinMax(Settings.MIN_MAX_VIEWS);
|
long[] minMax = Utils.parseMinMax(Settings.MIN_MAX_VIEWS);
|
||||||
minView = minMax[0];
|
minView = minMax[0];
|
||||||
maxView = minMax[1];
|
maxView = minMax[1];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting DOWNLOAD_WATERMARK = new BooleanSetting("down_watermark", TRUE);
|
public static final BooleanSetting DOWNLOAD_WATERMARK = new BooleanSetting("down_watermark", TRUE);
|
||||||
public static final BooleanSetting CLEAR_DISPLAY = new BooleanSetting("clear_display", FALSE);
|
public static final BooleanSetting CLEAR_DISPLAY = new BooleanSetting("clear_display", FALSE);
|
||||||
public static final FloatSetting REMEMBERED_SPEED = new FloatSetting("REMEMBERED_SPEED", 1.0f);
|
public static final FloatSetting REMEMBERED_SPEED = new FloatSetting("REMEMBERED_SPEED", 1.0f);
|
||||||
public static final BooleanSetting SIM_SPOOF = new BooleanSetting("simspoof", TRUE, true);
|
public static final BooleanSetting SIM_SPOOF = new BooleanSetting("simspoof", FALSE, true);
|
||||||
public static final StringSetting SIM_SPOOF_ISO = new StringSetting("simspoof_iso", "us");
|
public static final StringSetting SIM_SPOOF_ISO = new StringSetting("simspoof_iso", "us");
|
||||||
public static final StringSetting SIMSPOOF_MCCMNC = new StringSetting("simspoof_mccmnc", "310160");
|
public static final StringSetting SIMSPOOF_MCCMNC = new StringSetting("simspoof_mccmnc", "310160");
|
||||||
public static final StringSetting SIMSPOOF_OP_NAME = new StringSetting("simspoof_op_name", "T-Mobile");
|
public static final StringSetting SIMSPOOF_OP_NAME = new StringSetting("simspoof_op_name", "T-Mobile");
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import android.widget.RadioButton;
|
|||||||
import android.widget.RadioGroup;
|
import android.widget.RadioGroup;
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.StringSetting;
|
import app.revanced.extension.shared.settings.StringSetting;
|
||||||
|
import app.revanced.extension.tiktok.Utils;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class DownloadPathPreference extends DialogPreference {
|
public class DownloadPathPreference extends DialogPreference {
|
||||||
private final Context context;
|
|
||||||
private final String[] entryValues = {"DCIM", "Movies", "Pictures"};
|
private final String[] entryValues = {"DCIM", "Movies", "Pictures"};
|
||||||
private String mValue;
|
private String mValue;
|
||||||
|
|
||||||
@@ -29,11 +29,10 @@ public class DownloadPathPreference extends DialogPreference {
|
|||||||
|
|
||||||
public DownloadPathPreference(Context context, String title, StringSetting setting) {
|
public DownloadPathPreference(Context context, String title, StringSetting setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.context = context;
|
setTitle(title);
|
||||||
this.setTitle(title);
|
setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + setting.get());
|
||||||
this.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + setting.get());
|
setKey(setting.key);
|
||||||
this.setKey(setting.key);
|
setValue(setting.get());
|
||||||
this.setValue(setting.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
@@ -59,6 +58,7 @@ public class DownloadPathPreference extends DialogPreference {
|
|||||||
childDownloadPath = getValue().substring(getValue().indexOf("/") + 1);
|
childDownloadPath = getValue().substring(getValue().indexOf("/") + 1);
|
||||||
mediaPathIndex = findIndexOf(currentMedia);
|
mediaPathIndex = findIndexOf(currentMedia);
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
LinearLayout dialogView = new LinearLayout(context);
|
LinearLayout dialogView = new LinearLayout(context);
|
||||||
RadioGroup mediaPath = new RadioGroup(context);
|
RadioGroup mediaPath = new RadioGroup(context);
|
||||||
mediaPath.setLayoutParams(new RadioGroup.LayoutParams(-1, -2));
|
mediaPath.setLayoutParams(new RadioGroup.LayoutParams(-1, -2));
|
||||||
@@ -79,12 +79,10 @@ public class DownloadPathPreference extends DialogPreference {
|
|||||||
downloadPath.addTextChangedListener(new TextWatcher() {
|
downloadPath.addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -99,6 +97,13 @@ public class DownloadPathPreference extends DialogPreference {
|
|||||||
return dialogView;
|
return dialogView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view) {
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
builder.setTitle("Download Path");
|
builder.setTitle("Download Path");
|
||||||
|
|||||||
@@ -2,16 +2,26 @@ package app.revanced.extension.tiktok.settings.preference;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.StringSetting;
|
import app.revanced.extension.shared.settings.StringSetting;
|
||||||
|
import app.revanced.extension.tiktok.Utils;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public class InputTextPreference extends EditTextPreference {
|
public class InputTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
public InputTextPreference(Context context, String title, String summary, StringSetting setting) {
|
public InputTextPreference(Context context, String title, String summary, StringSetting setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.setTitle(title);
|
setTitle(title);
|
||||||
this.setSummary(summary);
|
setSummary(summary);
|
||||||
this.setKey(setting.key);
|
setKey(setting.key);
|
||||||
this.setText(setting.get());
|
setText(setting.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view) {
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.revanced.extension.tiktok.settings.preference;
|
package app.revanced.extension.tiktok.settings.preference;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@@ -14,11 +15,10 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.StringSetting;
|
import app.revanced.extension.shared.settings.StringSetting;
|
||||||
|
import app.revanced.extension.tiktok.Utils;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class RangeValuePreference extends DialogPreference {
|
public class RangeValuePreference extends DialogPreference {
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
private String minValue;
|
private String minValue;
|
||||||
|
|
||||||
private String maxValue;
|
private String maxValue;
|
||||||
@@ -29,7 +29,6 @@ public class RangeValuePreference extends DialogPreference {
|
|||||||
|
|
||||||
public RangeValuePreference(Context context, String title, String summary, StringSetting setting) {
|
public RangeValuePreference(Context context, String title, String summary, StringSetting setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.context = context;
|
|
||||||
setTitle(title);
|
setTitle(title);
|
||||||
setSummary(summary);
|
setSummary(summary);
|
||||||
setKey(setting.key);
|
setKey(setting.key);
|
||||||
@@ -53,41 +52,52 @@ public class RangeValuePreference extends DialogPreference {
|
|||||||
return mValue;
|
return mValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
@Override
|
@Override
|
||||||
protected View onCreateDialogView() {
|
protected View onCreateDialogView() {
|
||||||
minValue = getValue().split("-")[0];
|
minValue = getValue().split("-")[0];
|
||||||
maxValue = getValue().split("-")[1];
|
maxValue = getValue().split("-")[1];
|
||||||
|
|
||||||
|
Context context = getContext();
|
||||||
|
|
||||||
LinearLayout dialogView = new LinearLayout(context);
|
LinearLayout dialogView = new LinearLayout(context);
|
||||||
dialogView.setOrientation(LinearLayout.VERTICAL);
|
dialogView.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
|
||||||
|
// Min view
|
||||||
LinearLayout minView = new LinearLayout(context);
|
LinearLayout minView = new LinearLayout(context);
|
||||||
minView.setOrientation(LinearLayout.HORIZONTAL);
|
minView.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
dialogView.addView(minView);
|
||||||
|
|
||||||
TextView min = new TextView(context);
|
TextView min = new TextView(context);
|
||||||
min.setText("Min: ");
|
min.setText("Min: ");
|
||||||
minView.addView(min);
|
minView.addView(min);
|
||||||
|
|
||||||
EditText minEditText = new EditText(context);
|
EditText minEditText = new EditText(context);
|
||||||
minEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
minEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
minEditText.setText(minValue);
|
minEditText.setText(minValue);
|
||||||
minView.addView(minEditText);
|
minView.addView(minEditText);
|
||||||
dialogView.addView(minView);
|
|
||||||
|
// Max view
|
||||||
LinearLayout maxView = new LinearLayout(context);
|
LinearLayout maxView = new LinearLayout(context);
|
||||||
maxView.setOrientation(LinearLayout.HORIZONTAL);
|
maxView.setOrientation(LinearLayout.HORIZONTAL);
|
||||||
|
dialogView.addView(maxView);
|
||||||
|
|
||||||
TextView max = new TextView(context);
|
TextView max = new TextView(context);
|
||||||
max.setText("Max: ");
|
max.setText("Max: ");
|
||||||
maxView.addView(max);
|
maxView.addView(max);
|
||||||
|
|
||||||
EditText maxEditText = new EditText(context);
|
EditText maxEditText = new EditText(context);
|
||||||
maxEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
maxEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
maxEditText.setText(maxValue);
|
maxEditText.setText(maxValue);
|
||||||
maxView.addView(maxEditText);
|
maxView.addView(maxEditText);
|
||||||
dialogView.addView(maxView);
|
|
||||||
minEditText.addTextChangedListener(new TextWatcher() {
|
minEditText.addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -98,12 +108,10 @@ public class RangeValuePreference extends DialogPreference {
|
|||||||
maxEditText.addTextChangedListener(new TextWatcher() {
|
maxEditText.addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -111,12 +119,21 @@ public class RangeValuePreference extends DialogPreference {
|
|||||||
maxValue = editable.toString();
|
maxValue = editable.toString();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return dialogView;
|
return dialogView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view) {
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> this.onClick(dialog, DialogInterface.BUTTON_POSITIVE));
|
builder.setPositiveButton(android.R.string.ok, (dialog, which)
|
||||||
|
-> this.onClick(dialog, DialogInterface.BUTTON_POSITIVE));
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void syncSettingWithPreference(@NonNull @NotNull Preference pref,
|
protected void syncSettingWithPreference(@NonNull Preference pref,
|
||||||
@NonNull @NotNull Setting<?> setting,
|
@NonNull Setting<?> setting,
|
||||||
boolean applySettingToPreference) {
|
boolean applySettingToPreference) {
|
||||||
if (pref instanceof RangeValuePreference) {
|
if (pref instanceof RangeValuePreference) {
|
||||||
RangeValuePreference rangeValuePref = (RangeValuePreference) pref;
|
RangeValuePreference rangeValuePref = (RangeValuePreference) pref;
|
||||||
@@ -34,7 +34,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
final var context = getContext();
|
final var context = getActivity();
|
||||||
|
|
||||||
// Currently no resources can be compiled for TikTok (fails with aapt error).
|
// Currently no resources can be compiled for TikTok (fails with aapt error).
|
||||||
// So all TikTok Strings are hard coded in the extension.
|
// So all TikTok Strings are hard coded in the extension.
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
package app.revanced.extension.tiktok.settings.preference;
|
package app.revanced.extension.tiktok.settings.preference;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.view.View;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
|
||||||
|
import app.revanced.extension.tiktok.Utils;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public class ReVancedTikTokAboutPreference extends ReVancedAboutPreference {
|
public class ReVancedTikTokAboutPreference extends ReVancedAboutPreference {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,22 +25,11 @@ public class ReVancedTikTokAboutPreference extends ReVancedAboutPreference {
|
|||||||
"revanced_settings_about_links_header", "Official links"
|
"revanced_settings_about_links_header", "Official links"
|
||||||
);
|
);
|
||||||
|
|
||||||
{
|
|
||||||
//noinspection deprecation
|
|
||||||
setTitle("About");
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReVancedTikTokAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
public ReVancedTikTokAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
public ReVancedTikTokAboutPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
public ReVancedTikTokAboutPreference(Context context) {
|
public ReVancedTikTokAboutPreference(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
|
||||||
|
setTitle("About");
|
||||||
|
setSummary("About ReVanced");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -52,4 +43,11 @@ public class ReVancedTikTokAboutPreference extends ReVancedAboutPreference {
|
|||||||
|
|
||||||
return String.format(format, args);
|
return String.format(format, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view) {
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,26 @@ package app.revanced.extension.tiktok.settings.preference;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.SwitchPreference;
|
import android.preference.SwitchPreference;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
|
import app.revanced.extension.tiktok.Utils;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class TogglePreference extends SwitchPreference {
|
public class TogglePreference extends SwitchPreference {
|
||||||
|
|
||||||
public TogglePreference(Context context, String title, String summary, BooleanSetting setting) {
|
public TogglePreference(Context context, String title, String summary, BooleanSetting setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.setTitle(title);
|
setTitle(title);
|
||||||
this.setSummary(summary);
|
setSummary(summary);
|
||||||
this.setKey(setting.key);
|
setKey(setting.key);
|
||||||
this.setChecked(setting.get());
|
setChecked(setting.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindView(View view) {
|
||||||
|
super.onBindView(view);
|
||||||
|
|
||||||
|
Utils.setTitleAndSummaryColor(getContext(), view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,9 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.revanced.extension"
|
namespace = "app.revanced.extension"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 22
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":extensions:tumblr:stub"))
|
compileOnly(project(":extensions:tumblr:stub"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
|
android.namespace = "app.revanced.extension"
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
id(libs.plugins.android.library.get().pluginId)
|
id(libs.plugins.android.library.get().pluginId)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.revanced.extension"
|
namespace = "app.revanced.extension"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 26
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,3 +6,14 @@ dependencies {
|
|||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
compileOnly(libs.appcompat)
|
compileOnly(libs.appcompat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 21
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,14 +4,9 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "app.revanced.extension"
|
namespace = "app.revanced.extension"
|
||||||
compileSdk = 33
|
compileSdk = 34
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk = 24
|
minSdk = 21
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
//noinspection GradleDependency
|
|
||||||
android.compileSdk = 33
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly(project(":extensions:shared:library"))
|
compileOnly(project(":extensions:shared:library"))
|
||||||
compileOnly(project(":extensions:youtube:stub"))
|
compileOnly(project(":extensions:youtube:stub"))
|
||||||
compileOnly(libs.annotation)
|
compileOnly(libs.annotation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
minSdk = 26
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -90,4 +90,12 @@ public class ThemeHelper {
|
|||||||
public static int getForegroundColor() {
|
public static int getForegroundColor() {
|
||||||
return isDarkTheme() ? getLightThemeColor() : getDarkThemeColor();
|
return isDarkTheme() ? getLightThemeColor() : getDarkThemeColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getToolbarBackgroundColor() {
|
||||||
|
final String colorName = isDarkTheme()
|
||||||
|
? "yt_black3"
|
||||||
|
: "yt_white1";
|
||||||
|
|
||||||
|
return getColorInt(colorName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -176,14 +176,13 @@ public final class AlternativeThumbnailsPatch {
|
|||||||
// Unknown tab, treat as the home tab;
|
// Unknown tab, treat as the home tab;
|
||||||
return homeOption;
|
return homeOption;
|
||||||
}
|
}
|
||||||
if (selectedNavButton == NavigationButton.HOME) {
|
|
||||||
return homeOption;
|
return switch (selectedNavButton) {
|
||||||
}
|
case SUBSCRIPTIONS, NOTIFICATIONS -> subscriptionsOption;
|
||||||
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS || selectedNavButton == NavigationButton.NOTIFICATIONS) {
|
case LIBRARY -> libraryOption;
|
||||||
return subscriptionsOption;
|
// Home or explore tab.
|
||||||
}
|
default -> homeOption;
|
||||||
// A library tab variant is active.
|
};
|
||||||
return libraryOption;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ChangeFormFactorPatch {
|
||||||
|
|
||||||
|
public enum FormFactor {
|
||||||
|
/**
|
||||||
|
* Unmodified, and same as un-patched.
|
||||||
|
*/
|
||||||
|
DEFAULT(null),
|
||||||
|
/**
|
||||||
|
* <pre>
|
||||||
|
* Some changes include:
|
||||||
|
* - Explore tab is present.
|
||||||
|
* - watch history is missing.
|
||||||
|
* - feed thumbnails fade in.
|
||||||
|
*/
|
||||||
|
UNKNOWN(0),
|
||||||
|
SMALL(1),
|
||||||
|
LARGE(2),
|
||||||
|
/**
|
||||||
|
* Cars with 'Google built-in'.
|
||||||
|
* Layout seems identical to {@link #UNKNOWN}
|
||||||
|
* even when using an Android Automotive device.
|
||||||
|
*/
|
||||||
|
AUTOMOTIVE(3),
|
||||||
|
WEARABLE(4);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
final Integer formFactorType;
|
||||||
|
|
||||||
|
FormFactor(@Nullable Integer formFactorType) {
|
||||||
|
this.formFactorType = formFactorType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int getFormFactor(int original) {
|
||||||
|
return FORM_FACTOR_TYPE == null
|
||||||
|
? original
|
||||||
|
: FORM_FACTOR_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,26 +18,35 @@ public final class ChangeStartPagePatch {
|
|||||||
/**
|
/**
|
||||||
* Unmodified type, and same as un-patched.
|
* Unmodified type, and same as un-patched.
|
||||||
*/
|
*/
|
||||||
ORIGINAL("", null),
|
DEFAULT("", null),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Browse id.
|
* Browse id.
|
||||||
*/
|
*/
|
||||||
|
ALL_SUBSCRIPTIONS("FEchannels", TRUE),
|
||||||
BROWSE("FEguide_builder", TRUE),
|
BROWSE("FEguide_builder", TRUE),
|
||||||
EXPLORE("FEexplore", TRUE),
|
EXPLORE("FEexplore", TRUE),
|
||||||
HISTORY("FEhistory", TRUE),
|
HISTORY("FEhistory", TRUE),
|
||||||
LIBRARY("FElibrary", TRUE),
|
LIBRARY("FElibrary", TRUE),
|
||||||
MOVIE("FEstorefront", TRUE),
|
MOVIE("FEstorefront", TRUE),
|
||||||
|
NOTIFICATIONS("FEactivity", TRUE),
|
||||||
|
PLAYLISTS("FEplaylist_aggregation", TRUE),
|
||||||
SUBSCRIPTIONS("FEsubscriptions", TRUE),
|
SUBSCRIPTIONS("FEsubscriptions", TRUE),
|
||||||
TRENDING("FEtrending", TRUE),
|
TRENDING("FEtrending", TRUE),
|
||||||
|
YOUR_CLIPS("FEclips", TRUE),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel id, this can be used as a browseId.
|
* Channel id, this can be used as a browseId.
|
||||||
*/
|
*/
|
||||||
|
COURSES("UCtFRv9O2AHqOZjjynzrv-xg", TRUE),
|
||||||
|
FASHION("UCrpQ4p1Ql_hG8rKXIKM1MOQ", TRUE),
|
||||||
GAMING("UCOpNcN46UbXVtpKMrmU4Abg", TRUE),
|
GAMING("UCOpNcN46UbXVtpKMrmU4Abg", TRUE),
|
||||||
LIVE("UC4R8DWoMoI7CAwX8_LjQHig", TRUE),
|
LIVE("UC4R8DWoMoI7CAwX8_LjQHig", TRUE),
|
||||||
MUSIC("UC-9-kyTW8ZkZNDHQJ6FgpwQ", TRUE),
|
MUSIC("UC-9-kyTW8ZkZNDHQJ6FgpwQ", TRUE),
|
||||||
|
NEWS("UCYfdidRxbB8Qhf0Nx7ioOYw", TRUE),
|
||||||
|
SHOPPING("UCkYQyvc_i9hXEo4xic9Hh2g", TRUE),
|
||||||
SPORTS("UCEgdi0XIXXZ-qJOFPf4JSKw", TRUE),
|
SPORTS("UCEgdi0XIXXZ-qJOFPf4JSKw", TRUE),
|
||||||
|
VIRTUAL_REALITY("UCzuqhhs6NWbgTzMuM09WKDQ", TRUE),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Playlist id, this can be used as a browseId.
|
* Playlist id, this can be used as a browseId.
|
||||||
@@ -51,12 +60,12 @@ public final class ChangeStartPagePatch {
|
|||||||
SEARCH("com.google.android.youtube.action.open.search", FALSE),
|
SEARCH("com.google.android.youtube.action.open.search", FALSE),
|
||||||
SHORTS("com.google.android.youtube.action.open.shorts", FALSE);
|
SHORTS("com.google.android.youtube.action.open.shorts", FALSE);
|
||||||
|
|
||||||
@Nullable
|
|
||||||
final Boolean isBrowseId;
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
final Boolean isBrowseId;
|
||||||
|
|
||||||
StartPage(@NonNull String id, @Nullable Boolean isBrowseId) {
|
StartPage(@NonNull String id, @Nullable Boolean isBrowseId) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.isBrowseId = isBrowseId;
|
this.isBrowseId = isBrowseId;
|
||||||
@@ -122,7 +131,7 @@ public final class ChangeStartPagePatch {
|
|||||||
}
|
}
|
||||||
appLaunched = true;
|
appLaunched = true;
|
||||||
|
|
||||||
final String intentAction = START_PAGE.id;
|
String intentAction = START_PAGE.id;
|
||||||
Logger.printDebug(() -> "Changing intent action to " + intentAction);
|
Logger.printDebug(() -> "Changing intent action to " + intentAction);
|
||||||
intent.setAction(intentAction);
|
intent.setAction(intentAction);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class DisableHdrPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean disableHDRVideo() {
|
||||||
|
return !Settings.DISABLE_HDR_VIDEO.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -9,14 +9,23 @@ import app.revanced.extension.shared.settings.BaseSettings;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class EnableDebuggingPatch {
|
public final class EnableDebuggingPatch {
|
||||||
|
|
||||||
private static final ConcurrentMap<Long, Boolean> featureFlags
|
/**
|
||||||
= new ConcurrentHashMap<>(300, 0.75f, 1);
|
* Only log if debugging is enabled on startup.
|
||||||
|
* This prevents enabling debugging
|
||||||
|
* while the app is running then failing to restart
|
||||||
|
* resulting in an incomplete log.
|
||||||
|
*/
|
||||||
|
private static final boolean LOG_FEATURE_FLAGS = BaseSettings.DEBUG.get();
|
||||||
|
|
||||||
|
private static final ConcurrentMap<Long, Boolean> featureFlags = LOG_FEATURE_FLAGS
|
||||||
|
? new ConcurrentHashMap<>(800, 0.5f, 1)
|
||||||
|
: null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) {
|
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) {
|
||||||
if (value && BaseSettings.DEBUG.get()) {
|
if (LOG_FEATURE_FLAGS && value) {
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
Logger.printDebug(() -> "boolean feature is enabled: " + flag);
|
||||||
}
|
}
|
||||||
@@ -29,7 +38,7 @@ public final class EnableDebuggingPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) {
|
public static double isDoubleFeatureFlagEnabled(double value, long flag, double defaultValue) {
|
||||||
if (defaultValue != value && BaseSettings.DEBUG.get()) {
|
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
// Align the log outputs to make post processing easier.
|
// Align the log outputs to make post processing easier.
|
||||||
Logger.printDebug(() -> " double feature is enabled: " + flag
|
Logger.printDebug(() -> " double feature is enabled: " + flag
|
||||||
@@ -44,7 +53,7 @@ public final class EnableDebuggingPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) {
|
public static long isLongFeatureFlagEnabled(long value, long flag, long defaultValue) {
|
||||||
if (defaultValue != value && BaseSettings.DEBUG.get()) {
|
if (LOG_FEATURE_FLAGS && defaultValue != value) {
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
Logger.printDebug(() -> " long feature is enabled: " + flag
|
Logger.printDebug(() -> " long feature is enabled: " + flag
|
||||||
+ " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue));
|
+ " value: " + value + (defaultValue == 0 ? "" : " default: " + defaultValue));
|
||||||
@@ -58,7 +67,7 @@ public final class EnableDebuggingPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static String isStringFeatureFlagEnabled(String value, long flag, String defaultValue) {
|
public static String isStringFeatureFlagEnabled(String value, long flag, String defaultValue) {
|
||||||
if (BaseSettings.DEBUG.get() && !defaultValue.equals(value)) {
|
if (LOG_FEATURE_FLAGS && !defaultValue.equals(value)) {
|
||||||
if (featureFlags.putIfAbsent(flag, true) == null) {
|
if (featureFlags.putIfAbsent(flag, true) == null) {
|
||||||
Logger.printDebug(() -> " string feature is enabled: " + flag
|
Logger.printDebug(() -> " string feature is enabled: " + flag
|
||||||
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
|
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ExitFullscreenPatch {
|
||||||
|
|
||||||
|
public enum FullscreenMode {
|
||||||
|
DISABLED,
|
||||||
|
PORTRAIT,
|
||||||
|
LANDSCAPE,
|
||||||
|
PORTRAIT_LANDSCAPE,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void endOfVideoReached() {
|
||||||
|
try {
|
||||||
|
FullscreenMode mode = Settings.EXIT_FULLSCREEN.get();
|
||||||
|
if (mode == FullscreenMode.DISABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PlayerType.getCurrent() == PlayerType.WATCH_WHILE_FULLSCREEN) {
|
||||||
|
if (mode != FullscreenMode.PORTRAIT_LANDSCAPE) {
|
||||||
|
if (Utils.isLandscapeOrientation()) {
|
||||||
|
if (mode == FullscreenMode.PORTRAIT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (mode == FullscreenMode.LANDSCAPE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user cold launches the app and plays a video but does not
|
||||||
|
// tap to show the overlay controls, the fullscreen button is not
|
||||||
|
// set because the overlay controls are not attached.
|
||||||
|
// To fix this, push the perform click to the back fo the main thread,
|
||||||
|
// and by then the overlay controls will be visible since the video is now finished.
|
||||||
|
Utils.runOnMainThread(() -> {
|
||||||
|
ImageView button = PlayerControlsPatch.fullscreenButtonRef.get();
|
||||||
|
if (button == null) {
|
||||||
|
Logger.printDebug(() -> "Fullscreen button is null, cannot click");
|
||||||
|
} else {
|
||||||
|
Logger.printDebug(() -> "Clicking fullscreen button");
|
||||||
|
final boolean soundEffectsEnabled = button.isSoundEffectsEnabled();
|
||||||
|
button.setSoundEffectsEnabled(false);
|
||||||
|
button.performClick();
|
||||||
|
button.setSoundEffectsEnabled(soundEffectsEnabled);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "endOfVideoReached failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class FixPlaybackSpeedWhilePlayingPatch {
|
||||||
|
|
||||||
|
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
|
||||||
|
|
||||||
|
public static boolean playbackSpeedChanged(float playbackSpeed) {
|
||||||
|
if (playbackSpeed == DEFAULT_YOUTUBE_PLAYBACK_SPEED &&
|
||||||
|
PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Blocking call to change playback speed to 1.0x");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
|
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class ForceOriginalAudioPatch {
|
||||||
|
|
||||||
|
private static final String DEFAULT_AUDIO_TRACKS_SUFFIX = ".4";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the conditions to use this patch were present when the app launched.
|
||||||
|
*/
|
||||||
|
public static boolean PATCH_AVAILABLE = SpoofVideoStreamsPatch.notSpoofingToAndroid();
|
||||||
|
|
||||||
|
public static final class ForceOriginalAudioAvailability implements Setting.Availability {
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
// Check conditions of launch and now. Otherwise if spoofing is changed
|
||||||
|
// without a restart the setting will show as available when it's not.
|
||||||
|
return PATCH_AVAILABLE && SpoofVideoStreamsPatch.notSpoofingToAndroid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean isDefaultAudioStream(boolean isDefault, String audioTrackId, String audioTrackDisplayName) {
|
||||||
|
try {
|
||||||
|
if (!Settings.FORCE_ORIGINAL_AUDIO.get()) {
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (audioTrackId.isEmpty()) {
|
||||||
|
// Older app targets can have empty audio tracks and these might be placeholders.
|
||||||
|
// The real audio tracks are called after these.
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "default: " + String.format("%-5s", isDefault) + " id: "
|
||||||
|
+ String.format("%-8s", audioTrackId) + " name:" + audioTrackDisplayName);
|
||||||
|
|
||||||
|
final boolean isOriginal = audioTrackId.endsWith(DEFAULT_AUDIO_TRACKS_SUFFIX);
|
||||||
|
if (isOriginal) {
|
||||||
|
Logger.printDebug(() -> "Using audio: " + audioTrackId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isOriginal;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "isDefaultAudioStream failure", ex);
|
||||||
|
|
||||||
|
return isDefault;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,7 +30,7 @@ public final class MiniplayerPatch {
|
|||||||
*/
|
*/
|
||||||
DISABLED(false, null),
|
DISABLED(false, null),
|
||||||
/** Unmodified type, and same as un-patched. */
|
/** Unmodified type, and same as un-patched. */
|
||||||
ORIGINAL(null, null),
|
DEFAULT(null, null),
|
||||||
/**
|
/**
|
||||||
* Exactly the same as MINIMAL and only here for migration of user settings.
|
* Exactly the same as MINIMAL and only here for migration of user settings.
|
||||||
* Eventually this should be deleted.
|
* Eventually this should be deleted.
|
||||||
@@ -82,7 +82,13 @@ public final class MiniplayerPatch {
|
|||||||
final int WIDTH_DIP_MIN = 170; // Seems to be the smallest that works.
|
final int WIDTH_DIP_MIN = 170; // Seems to be the smallest that works.
|
||||||
final int HORIZONTAL_PADDING_DIP = 15; // Estimated padding.
|
final int HORIZONTAL_PADDING_DIP = 15; // Estimated padding.
|
||||||
// Round down to the nearest 5 pixels, to keep any error toasts easier to read.
|
// Round down to the nearest 5 pixels, to keep any error toasts easier to read.
|
||||||
final int WIDTH_DIP_MAX = 5 * ((deviceDipWidth - HORIZONTAL_PADDING_DIP) / 5);
|
final int estimatedWidthDipMax = 5 * ((deviceDipWidth - HORIZONTAL_PADDING_DIP) / 5);
|
||||||
|
// On some ultra low end devices the pixel width and density are the same number,
|
||||||
|
// which causes the estimate to always give a value of 1.
|
||||||
|
// Fix this by using a fixed size of double the min width.
|
||||||
|
final int WIDTH_DIP_MAX = estimatedWidthDipMax <= WIDTH_DIP_MIN
|
||||||
|
? 2 * WIDTH_DIP_MIN
|
||||||
|
: estimatedWidthDipMax;
|
||||||
Logger.printDebug(() -> "Screen dip width: " + deviceDipWidth + " maxWidth: " + WIDTH_DIP_MAX);
|
Logger.printDebug(() -> "Screen dip width: " + deviceDipWidth + " maxWidth: " + WIDTH_DIP_MAX);
|
||||||
|
|
||||||
int dipWidth = Settings.MINIPLAYER_WIDTH_DIP.get();
|
int dipWidth = Settings.MINIPLAYER_WIDTH_DIP.get();
|
||||||
@@ -127,8 +133,10 @@ public final class MiniplayerPatch {
|
|||||||
private static final boolean HIDE_SUBTEXT_ENABLED =
|
private static final boolean HIDE_SUBTEXT_ENABLED =
|
||||||
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_HIDE_SUBTEXT.get();
|
(CURRENT_TYPE == MODERN_1 || CURRENT_TYPE == MODERN_3) && Settings.MINIPLAYER_HIDE_SUBTEXT.get();
|
||||||
|
|
||||||
private static final boolean HIDE_REWIND_FORWARD_ENABLED =
|
// 19.25 is last version that has forward/back buttons for phones,
|
||||||
CURRENT_TYPE == MODERN_1 && Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get();
|
// but buttons still show for tablets/foldable devices and they don't work well so always hide.
|
||||||
|
private static final boolean HIDE_REWIND_FORWARD_ENABLED = CURRENT_TYPE == MODERN_1
|
||||||
|
&& (VersionCheckPatch.IS_19_34_OR_GREATER || Settings.MINIPLAYER_HIDE_REWIND_FORWARD.get());
|
||||||
|
|
||||||
private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED =
|
private static final boolean MINIPLAYER_ROUNDED_CORNERS_ENABLED =
|
||||||
Settings.MINIPLAYER_ROUNDED_CORNERS.get();
|
Settings.MINIPLAYER_ROUNDED_CORNERS.get();
|
||||||
@@ -145,6 +153,18 @@ public final class MiniplayerPatch {
|
|||||||
|
|
||||||
private static final int OPACITY_LEVEL;
|
private static final int OPACITY_LEVEL;
|
||||||
|
|
||||||
|
static {
|
||||||
|
int opacity = Settings.MINIPLAYER_OPACITY.get();
|
||||||
|
|
||||||
|
if (opacity < 0 || opacity > 100) {
|
||||||
|
Utils.showToastLong(str("revanced_miniplayer_opacity_invalid_toast"));
|
||||||
|
Settings.MINIPLAYER_OPACITY.resetToDefault();
|
||||||
|
opacity = Settings.MINIPLAYER_OPACITY.defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
OPACITY_LEVEL = (opacity * 255) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
public static final class MiniplayerHorizontalDragAvailability implements Setting.Availability {
|
public static final class MiniplayerHorizontalDragAvailability implements Setting.Availability {
|
||||||
@Override
|
@Override
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
@@ -163,18 +183,6 @@ public final class MiniplayerPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
|
||||||
int opacity = Settings.MINIPLAYER_OPACITY.get();
|
|
||||||
|
|
||||||
if (opacity < 0 || opacity > 100) {
|
|
||||||
Utils.showToastLong(str("revanced_miniplayer_opacity_invalid_toast"));
|
|
||||||
Settings.MINIPLAYER_OPACITY.resetToDefault();
|
|
||||||
opacity = Settings.MINIPLAYER_OPACITY.defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
OPACITY_LEVEL = (opacity * 255) / 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
*
|
||||||
@@ -182,7 +190,7 @@ public final class MiniplayerPatch {
|
|||||||
* effectively disabling the miniplayer.
|
* effectively disabling the miniplayer.
|
||||||
*/
|
*/
|
||||||
public static boolean getMiniplayerOnCloseHandler(boolean original) {
|
public static boolean getMiniplayerOnCloseHandler(boolean original) {
|
||||||
return CURRENT_TYPE == ORIGINAL
|
return CURRENT_TYPE == DEFAULT
|
||||||
? original
|
? original
|
||||||
: CURRENT_TYPE == DISABLED;
|
: CURRENT_TYPE == DISABLED;
|
||||||
}
|
}
|
||||||
@@ -201,7 +209,7 @@ public final class MiniplayerPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean getModernMiniplayerOverride(boolean original) {
|
public static boolean getModernMiniplayerOverride(boolean original) {
|
||||||
return CURRENT_TYPE == ORIGINAL
|
return CURRENT_TYPE == DEFAULT
|
||||||
? original
|
? original
|
||||||
: CURRENT_TYPE.isModern();
|
: CURRENT_TYPE.isModern();
|
||||||
}
|
}
|
||||||
@@ -229,7 +237,7 @@ public final class MiniplayerPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean getModernFeatureFlagsActiveOverride(boolean original) {
|
public static boolean getModernFeatureFlagsActiveOverride(boolean original) {
|
||||||
if (CURRENT_TYPE == ORIGINAL) {
|
if (CURRENT_TYPE == DEFAULT) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +248,7 @@ public final class MiniplayerPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean enableMiniplayerDoubleTapAction(boolean original) {
|
public static boolean enableMiniplayerDoubleTapAction(boolean original) {
|
||||||
if (CURRENT_TYPE == ORIGINAL) {
|
if (CURRENT_TYPE == DEFAULT) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +259,7 @@ public final class MiniplayerPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean enableMiniplayerDragAndDrop(boolean original) {
|
public static boolean enableMiniplayerDragAndDrop(boolean original) {
|
||||||
if (CURRENT_TYPE == ORIGINAL) {
|
if (CURRENT_TYPE == DEFAULT) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ package app.revanced.extension.youtube.patches;
|
|||||||
import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
|
import static app.revanced.extension.shared.Utils.hideViewUnderCondition;
|
||||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -18,6 +21,7 @@ public final class NavigationButtonsPatch {
|
|||||||
{
|
{
|
||||||
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
|
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
|
||||||
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
|
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
|
||||||
|
put(NavigationButton.NOTIFICATIONS, Settings.HIDE_NOTIFICATIONS_BUTTON.get());
|
||||||
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
|
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
|
||||||
put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
||||||
}
|
}
|
||||||
@@ -26,6 +30,15 @@ public final class NavigationButtonsPatch {
|
|||||||
private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
||||||
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
||||||
|
|
||||||
|
private static final Boolean DISABLE_TRANSLUCENT_STATUS_BAR
|
||||||
|
= Settings.DISABLE_TRANSLUCENT_STATUS_BAR.get();
|
||||||
|
|
||||||
|
private static final Boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT
|
||||||
|
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT.get();
|
||||||
|
|
||||||
|
private static final Boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
||||||
|
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@@ -48,4 +61,42 @@ public final class NavigationButtonsPatch {
|
|||||||
public static void hideNavigationButtonLabels(TextView navigationLabelsView) {
|
public static void hideNavigationButtonLabels(TextView navigationLabelsView) {
|
||||||
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
|
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean useTranslucentNavigationStatusBar(boolean original) {
|
||||||
|
// Must check Android version, as forcing this on Android 11 or lower causes app hang and crash.
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DISABLE_TRANSLUCENT_STATUS_BAR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean useTranslucentNavigationButtons(boolean original) {
|
||||||
|
// Feature requires Android 13+
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK && !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK && DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.isDarkModeEnabled(Utils.getContext())
|
||||||
|
? !DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
|
||||||
|
: !DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class OpenShortsInRegularPlayerPatch {
|
||||||
|
|
||||||
|
public enum ShortsPlayerType {
|
||||||
|
SHORTS_PLAYER,
|
||||||
|
REGULAR_PLAYER,
|
||||||
|
REGULAR_PLAYER_FULLSCREEN
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
if (!VersionCheckPatch.IS_19_46_OR_GREATER
|
||||||
|
&& Settings.SHORTS_PLAYER_TYPE.get() == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN) {
|
||||||
|
// User imported newer settings to an older app target.
|
||||||
|
Logger.printInfo(() -> "Resetting " + Settings.SHORTS_PLAYER_TYPE);
|
||||||
|
Settings.SHORTS_PLAYER_TYPE.resetToDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WeakReference<Activity> mainActivityRef = new WeakReference<>(null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static void setMainActivity(Activity activity) {
|
||||||
|
mainActivityRef = new WeakReference<>(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean openShort(String videoID) {
|
||||||
|
try {
|
||||||
|
ShortsPlayerType type = Settings.SHORTS_PLAYER_TYPE.get();
|
||||||
|
if (type == ShortsPlayerType.SHORTS_PLAYER) {
|
||||||
|
return false; // Default unpatched behavior.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoID.isEmpty()) {
|
||||||
|
// Shorts was opened using launcher app shortcut.
|
||||||
|
//
|
||||||
|
// This check will not detect if the Shorts app shortcut is used
|
||||||
|
// while the app is running in the background (instead the regular player is opened).
|
||||||
|
// To detect that the hooked method map parameter can be checked
|
||||||
|
// if integer key 'com.google.android.apps.youtube.app.endpoint.flags'
|
||||||
|
// has bitmask 16 set.
|
||||||
|
//
|
||||||
|
// This use case seems unlikely if the user has the Shorts
|
||||||
|
// set to open in the regular player, so it's ignored as
|
||||||
|
// checking the map makes the patch more complicated.
|
||||||
|
Logger.printDebug(() -> "Ignoring Short with no videoId");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (NavigationButton.getSelectedNavigationButton() == NavigationButton.SHORTS) {
|
||||||
|
return false; // Always use Shorts player for the Shorts nav button.
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean forceFullScreen = (type == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN);
|
||||||
|
OpenVideosFullscreenHookPatch.setOpenNextVideoFullscreen(forceFullScreen);
|
||||||
|
|
||||||
|
// Can use the application context and add intent flags of
|
||||||
|
// FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TOP
|
||||||
|
// But the activity context seems to fix random app crashes
|
||||||
|
// if Shorts urls are opened outside the app.
|
||||||
|
var context = mainActivityRef.get();
|
||||||
|
|
||||||
|
Intent videoPlayerIntent = new Intent(
|
||||||
|
Intent.ACTION_VIEW,
|
||||||
|
Uri.parse("https://youtube.com/watch?v=" + videoID)
|
||||||
|
);
|
||||||
|
videoPlayerIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
videoPlayerIntent.setPackage(context.getPackageName());
|
||||||
|
|
||||||
|
context.startActivity(videoPlayerIntent);
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
OpenVideosFullscreenHookPatch.setOpenNextVideoFullscreen(null);
|
||||||
|
Logger.printException(() -> "openShort failure", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class OpenVideosFullscreenHookPatch {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static volatile Boolean openNextVideoFullscreen;
|
||||||
|
|
||||||
|
public static void setOpenNextVideoFullscreen(@Nullable Boolean forceFullScreen) {
|
||||||
|
openNextVideoFullscreen = forceFullScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changed during patching since this class is also
|
||||||
|
* used by {@link OpenVideosFullscreenHookPatch}.
|
||||||
|
*/
|
||||||
|
private static boolean isFullScreenPatchIncluded() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean openVideoFullscreenPortrait(boolean original) {
|
||||||
|
Boolean openFullscreen = openNextVideoFullscreen;
|
||||||
|
if (openFullscreen != null) {
|
||||||
|
openNextVideoFullscreen = null;
|
||||||
|
return openFullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFullScreenPatchIncluded()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,15 +4,30 @@ import android.view.View;
|
|||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class PlayerControlsPatch {
|
public class PlayerControlsPatch {
|
||||||
|
|
||||||
|
public static WeakReference<ImageView> fullscreenButtonRef = new WeakReference<>(null);
|
||||||
|
|
||||||
|
private static boolean fullscreenButtonVisibilityCallbacksExist() {
|
||||||
|
return false; // Modified during patching if needed.
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void setFullscreenCloseButton(ImageView imageButton) {
|
public static void setFullscreenCloseButton(ImageView imageButton) {
|
||||||
|
fullscreenButtonRef = new WeakReference<>(imageButton);
|
||||||
|
Logger.printDebug(() -> "Fullscreen button set");
|
||||||
|
|
||||||
|
if (!fullscreenButtonVisibilityCallbacksExist()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Add a global listener, since the protected method
|
// Add a global listener, since the protected method
|
||||||
// View#onVisibilityChanged() does not have any call backs.
|
// View#onVisibilityChanged() does not have any call backs.
|
||||||
imageButton.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
imageButton.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||||
@@ -39,7 +54,7 @@ public class PlayerControlsPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// noinspection EmptyMethod
|
// noinspection EmptyMethod
|
||||||
public static void fullscreenButtonVisibilityChanged(boolean isVisible) {
|
private static void fullscreenButtonVisibilityChanged(boolean isVisible) {
|
||||||
// Code added during patching.
|
// Code added during patching.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import static app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeD
|
|||||||
|
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@@ -366,9 +365,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();
|
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();
|
||||||
|
|
||||||
private static void clearRemovedShortsTextViews() {
|
private static void clearRemovedShortsTextViews() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // YouTube requires Android N or greater
|
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
|
||||||
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
package app.revanced.extension.youtube.patches;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
@@ -58,7 +55,6 @@ public class ShortsAutoplayPatch {
|
|||||||
/**
|
/**
|
||||||
* @return If the app is currently in background PiP mode.
|
* @return If the app is currently in background PiP mode.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
private static boolean isAppInBackgroundPiPMode() {
|
private static boolean isAppInBackgroundPiPMode() {
|
||||||
Activity activity = mainActivityRef.get();
|
Activity activity = mainActivityRef.get();
|
||||||
return activity != null && activity.isInPictureInPictureMode();
|
return activity != null && activity.isInPictureInPictureMode();
|
||||||
@@ -80,7 +76,6 @@ public class ShortsAutoplayPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) {
|
public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) {
|
||||||
try {
|
try {
|
||||||
final boolean autoplay;
|
final boolean autoplay;
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.patches;
|
|
||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public final class TabletLayoutPatch {
|
|
||||||
|
|
||||||
private static final boolean TABLET_LAYOUT_ENABLED = Settings.TABLET_LAYOUT.get();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean getTabletLayoutEnabled() {
|
|
||||||
return TABLET_LAYOUT_ENABLED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,4 +9,5 @@ public class VersionCheckPatch {
|
|||||||
public static final boolean IS_19_26_OR_GREATER = Utils.getAppVersionName().compareTo("19.26.00") >= 0;
|
public static final boolean IS_19_26_OR_GREATER = Utils.getAppVersionName().compareTo("19.26.00") >= 0;
|
||||||
public static final boolean IS_19_29_OR_GREATER = Utils.getAppVersionName().compareTo("19.29.00") >= 0;
|
public static final boolean IS_19_29_OR_GREATER = Utils.getAppVersionName().compareTo("19.29.00") >= 0;
|
||||||
public static final boolean IS_19_34_OR_GREATER = Utils.getAppVersionName().compareTo("19.34.00") >= 0;
|
public static final boolean IS_19_34_OR_GREATER = Utils.getAppVersionName().compareTo("19.34.00") >= 0;
|
||||||
|
public static final boolean IS_19_46_OR_GREATER = Utils.getAppVersionName().compareTo("19.46.00") >= 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class VideoAdsPatch {
|
public class VideoAdsPatch {
|
||||||
|
|
||||||
// Used by app.revanced.patches.youtube.ad.general.video.patch.VideoAdsPatch
|
private static final boolean SHOW_VIDEO_ADS = !Settings.HIDE_VIDEO_ADS.get();
|
||||||
// depends on Whitelist patch (still needs to be written)
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
public static boolean shouldShowAds() {
|
public static boolean shouldShowAds() {
|
||||||
return !Settings.HIDE_VIDEO_ADS.get(); // TODO && Whitelist.shouldShowAds();
|
return SHOW_VIDEO_ADS;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,10 @@ import static app.revanced.extension.youtube.patches.announcements.requests.Anno
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.method.LinkMovementMethod;
|
import android.text.method.LinkMovementMethod;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -31,7 +28,6 @@ public final class AnnouncementsPatch {
|
|||||||
private AnnouncementsPatch() {
|
private AnnouncementsPatch() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
private static boolean isLatestAlready() throws IOException {
|
private static boolean isLatestAlready() throws IOException {
|
||||||
HttpURLConnection connection =
|
HttpURLConnection connection =
|
||||||
AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
|
AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
|
||||||
@@ -70,7 +66,6 @@ public final class AnnouncementsPatch {
|
|||||||
return Settings.ANNOUNCEMENT_LAST_ID.get() == id;
|
return Settings.ANNOUNCEMENT_LAST_ID.get() == id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
public static void showAnnouncement(final Activity context) {
|
public static void showAnnouncement(final Activity context) {
|
||||||
if (!Settings.ANNOUNCEMENTS.get()) return;
|
if (!Settings.ANNOUNCEMENTS.get()) return;
|
||||||
|
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import android.view.View;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
import app.revanced.extension.youtube.StringTrieSearch;
|
import app.revanced.extension.youtube.StringTrieSearch;
|
||||||
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class AdsFilter extends Filter {
|
public final class AdsFilter extends Filter {
|
||||||
@@ -22,6 +24,11 @@ public final class AdsFilter extends Filter {
|
|||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
|
// https://encrypted-tbn0.gstatic.com/shopping?q=abc
|
||||||
|
private static final String STORE_BANNER_DOMAIN = "gstatic.com/shopping";
|
||||||
|
private static final boolean HIDE_END_SCREEN_STORE_BANNER =
|
||||||
|
Settings.HIDE_END_SCREEN_STORE_BANNER.get();
|
||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
|
|
||||||
private final StringFilterGroup playerShoppingShelf;
|
private final StringFilterGroup playerShoppingShelf;
|
||||||
@@ -66,7 +73,9 @@ public final class AdsFilter extends Filter {
|
|||||||
"full_width_square_image_layout",
|
"full_width_square_image_layout",
|
||||||
"video_display_button_group_layout",
|
"video_display_button_group_layout",
|
||||||
"landscape_image_wide_button_layout",
|
"landscape_image_wide_button_layout",
|
||||||
"video_display_carousel_button_group_layout"
|
"video_display_carousel_button_group_layout",
|
||||||
|
"compact_landscape_image_layout", // Tablet layout search results.
|
||||||
|
"text_image_no_button_layout" // Tablet layout search results.
|
||||||
);
|
);
|
||||||
|
|
||||||
final var generalAds = new StringFilterGroup(
|
final var generalAds = new StringFilterGroup(
|
||||||
@@ -85,7 +94,8 @@ public final class AdsFilter extends Filter {
|
|||||||
"composite_concurrent_carousel_layout",
|
"composite_concurrent_carousel_layout",
|
||||||
"carousel_headered_layout",
|
"carousel_headered_layout",
|
||||||
"full_width_portrait_image_layout",
|
"full_width_portrait_image_layout",
|
||||||
"brand_video_shelf"
|
"brand_video_shelf",
|
||||||
|
"brand_video_singleton"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var movieAds = new StringFilterGroup(
|
final var movieAds = new StringFilterGroup(
|
||||||
@@ -111,23 +121,24 @@ public final class AdsFilter extends Filter {
|
|||||||
"expandable_list"
|
"expandable_list"
|
||||||
);
|
);
|
||||||
|
|
||||||
channelProfile = new StringFilterGroup(
|
|
||||||
null,
|
|
||||||
"channel_profile.eml"
|
|
||||||
);
|
|
||||||
|
|
||||||
playerShoppingShelf = new StringFilterGroup(
|
playerShoppingShelf = new StringFilterGroup(
|
||||||
null,
|
Settings.HIDE_PLAYER_STORE_SHELF,
|
||||||
"horizontal_shelf.eml"
|
"horizontal_shelf.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
playerShoppingShelfBuffer = new ByteArrayFilterGroup(
|
playerShoppingShelfBuffer = new ByteArrayFilterGroup(
|
||||||
Settings.HIDE_PLAYER_STORE_SHELF,
|
null,
|
||||||
"shopping_item_card_list.eml"
|
"shopping_item_card_list.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
visitStoreButton = new ByteArrayFilterGroup(
|
channelProfile = new StringFilterGroup(
|
||||||
Settings.HIDE_VISIT_STORE_BUTTON,
|
Settings.HIDE_VISIT_STORE_BUTTON,
|
||||||
|
"channel_profile.eml",
|
||||||
|
"page_header.eml"
|
||||||
|
);
|
||||||
|
|
||||||
|
visitStoreButton = new ByteArrayFilterGroup(
|
||||||
|
null,
|
||||||
"header_store_button"
|
"header_store_button"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -171,6 +182,11 @@ public final class AdsFilter extends Filter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for the index because of likelihood of false positives.
|
||||||
|
if (matchedGroup == shoppingLinks && contentIndex != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (exceptions.matches(path))
|
if (exceptions.matches(path))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -187,13 +203,25 @@ public final class AdsFilter extends Filter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for the index because of likelihood of false positives.
|
|
||||||
if (matchedGroup == shoppingLinks && contentIndex != 0)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* @param elementsList List of components of the end screen container.
|
||||||
|
* @param protobufList Component (ProtobufList).
|
||||||
|
*/
|
||||||
|
public static void hideEndScreenStoreBanner(List<Object> elementsList, Object protobufList) {
|
||||||
|
if (HIDE_END_SCREEN_STORE_BANNER && protobufList.toString().contains(STORE_BANNER_DOMAIN)) {
|
||||||
|
Logger.printDebug(() -> "Hiding store banner");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
elementsList.add(protobufList);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide the view, which shows ads in the homepage.
|
* Hide the view, which shows ads in the homepage.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -6,8 +6,12 @@ import app.revanced.extension.youtube.settings.Settings;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
final class ButtonsFilter extends Filter {
|
final class ButtonsFilter extends Filter {
|
||||||
|
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.eml";
|
||||||
|
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
|
||||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
||||||
|
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
|
||||||
|
|
||||||
|
private final StringFilterGroup likeSubscribeGlow;
|
||||||
private final StringFilterGroup actionBarGroup;
|
private final StringFilterGroup actionBarGroup;
|
||||||
private final StringFilterGroup bufferFilterPathGroup;
|
private final StringFilterGroup bufferFilterPathGroup;
|
||||||
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
|
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
|
||||||
@@ -20,11 +24,19 @@ final class ButtonsFilter extends Filter {
|
|||||||
addIdentifierCallbacks(actionBarGroup);
|
addIdentifierCallbacks(actionBarGroup);
|
||||||
|
|
||||||
|
|
||||||
|
likeSubscribeGlow = new StringFilterGroup(
|
||||||
|
Settings.DISABLE_LIKE_SUBSCRIBE_GLOW,
|
||||||
|
"animated_button_border.eml"
|
||||||
|
);
|
||||||
|
|
||||||
bufferFilterPathGroup = new StringFilterGroup(
|
bufferFilterPathGroup = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"|ContainerType|button.eml|"
|
"|ContainerType|button.eml|"
|
||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
|
likeSubscribeGlow,
|
||||||
|
bufferFilterPathGroup,
|
||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_LIKE_DISLIKE_BUTTON,
|
Settings.HIDE_LIKE_DISLIKE_BUTTON,
|
||||||
"|segmented_like_dislike_button"
|
"|segmented_like_dislike_button"
|
||||||
@@ -40,8 +52,7 @@ final class ButtonsFilter extends Filter {
|
|||||||
new StringFilterGroup(
|
new StringFilterGroup(
|
||||||
Settings.HIDE_CLIP_BUTTON,
|
Settings.HIDE_CLIP_BUTTON,
|
||||||
"|clip_button.eml|"
|
"|clip_button.eml|"
|
||||||
),
|
)
|
||||||
bufferFilterPathGroup
|
|
||||||
);
|
);
|
||||||
|
|
||||||
bufferButtonsGroupList.addAll(
|
bufferButtonsGroupList.addAll(
|
||||||
@@ -83,6 +94,15 @@ final class ButtonsFilter extends Filter {
|
|||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
if (matchedGroup == likeSubscribeGlow) {
|
||||||
|
if ((path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
||||||
|
&& path.contains(ANIMATED_VECTOR_TYPE_PATH)) {
|
||||||
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// If the current matched group is the action bar group,
|
// If the current matched group is the action bar group,
|
||||||
// in case every filter group is enabled, hide the action bar.
|
// in case every filter group is enabled, hide the action bar.
|
||||||
if (matchedGroup == actionBarGroup) {
|
if (matchedGroup == actionBarGroup) {
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ final class CommentsFilter extends Filter {
|
|||||||
private final ByteArrayFilterGroup emojiPickerBufferGroup;
|
private final ByteArrayFilterGroup emojiPickerBufferGroup;
|
||||||
|
|
||||||
public CommentsFilter() {
|
public CommentsFilter() {
|
||||||
|
var chatSummary = new StringFilterGroup(
|
||||||
|
Settings.HIDE_COMMENTS_CHAT_SUMMARY,
|
||||||
|
"live_chat_summary_banner.eml"
|
||||||
|
);
|
||||||
|
|
||||||
var commentsByMembers = new StringFilterGroup(
|
var commentsByMembers = new StringFilterGroup(
|
||||||
Settings.HIDE_COMMENTS_BY_MEMBERS_HEADER,
|
Settings.HIDE_COMMENTS_BY_MEMBERS_HEADER,
|
||||||
"sponsorships_comments_header.eml",
|
"sponsorships_comments_header.eml",
|
||||||
@@ -54,6 +59,7 @@ final class CommentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
|
chatSummary,
|
||||||
commentsByMembers,
|
commentsByMembers,
|
||||||
comments,
|
comments,
|
||||||
createAShort,
|
createAShort,
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
"transcript_section"
|
"transcript_section"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final StringFilterGroup howThisWasMadeSection = new StringFilterGroup(
|
||||||
|
Settings.HIDE_HOW_THIS_WAS_MADE_SECTION,
|
||||||
|
"how_this_was_made_section"
|
||||||
|
);
|
||||||
|
|
||||||
macroMarkersCarousel = new StringFilterGroup(
|
macroMarkersCarousel = new StringFilterGroup(
|
||||||
null,
|
null,
|
||||||
"macro_markers_carousel.eml"
|
"macro_markers_carousel.eml"
|
||||||
@@ -64,6 +69,7 @@ final class DescriptionComponentsFilter extends Filter {
|
|||||||
addPathCallbacks(
|
addPathCallbacks(
|
||||||
attributesSection,
|
attributesSection,
|
||||||
infoCardsSection,
|
infoCardsSection,
|
||||||
|
howThisWasMadeSection,
|
||||||
podcastSection,
|
podcastSection,
|
||||||
transcriptSection,
|
transcriptSection,
|
||||||
macroMarkersCarousel
|
macroMarkersCarousel
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
package app.revanced.extension.youtube.patches.components;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@@ -44,13 +41,11 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
|
|||||||
return filterGroups.iterator();
|
return filterGroups.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
@Override
|
@Override
|
||||||
public void forEach(@NonNull Consumer<? super T> action) {
|
public void forEach(@NonNull Consumer<? super T> action) {
|
||||||
filterGroups.forEach(action);
|
filterGroups.forEach(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Spliterator<T> spliterator() {
|
public Spliterator<T> spliterator() {
|
||||||
|
|||||||
@@ -4,11 +4,8 @@ import static app.revanced.extension.shared.StringRef.str;
|
|||||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||||
import static java.lang.Character.UnicodeBlock.*;
|
import static java.lang.Character.UnicodeBlock.*;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
@@ -44,7 +41,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||||||
* - When using whole word syntax, some keywords may need additional pluralized variations.
|
* - When using whole word syntax, some keywords may need additional pluralized variations.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
final class KeywordContentFilter extends Filter {
|
final class KeywordContentFilter extends Filter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -528,14 +524,13 @@ final class KeywordContentFilter extends Filter {
|
|||||||
if (selectedNavButton == null) {
|
if (selectedNavButton == null) {
|
||||||
return hideHome; // Unknown tab, treat the same as home.
|
return hideHome; // Unknown tab, treat the same as home.
|
||||||
}
|
}
|
||||||
if (selectedNavButton == NavigationButton.HOME) {
|
|
||||||
return hideHome;
|
return switch (selectedNavButton) {
|
||||||
}
|
case HOME, EXPLORE -> hideHome;
|
||||||
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
case SUBSCRIPTIONS -> hideSubscriptions;
|
||||||
return hideSubscriptions;
|
// User is in the Library or notifications.
|
||||||
}
|
default -> false;
|
||||||
// User is in the Library or Notifications tab.
|
};
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStats(boolean videoWasHidden, @Nullable String keyword) {
|
private void updateStats(boolean videoWasHidden, @Nullable String keyword) {
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ package app.revanced.extension.youtube.patches.components;
|
|||||||
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Build;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
@@ -18,10 +16,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
|
|||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public final class LayoutComponentsFilter extends Filter {
|
public final class LayoutComponentsFilter extends Filter {
|
||||||
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.eml";
|
|
||||||
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
|
|
||||||
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
|
|
||||||
|
|
||||||
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch(
|
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch(
|
||||||
"V.ED", // Playlist browse id.
|
"V.ED", // Playlist browse id.
|
||||||
"java.lang.ref.WeakReference"
|
"java.lang.ref.WeakReference"
|
||||||
@@ -36,19 +30,17 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||||
private final StringFilterGroup searchResultShelfHeader;
|
|
||||||
private final StringFilterGroup inFeedSurvey;
|
private final StringFilterGroup inFeedSurvey;
|
||||||
private final StringFilterGroup notifyMe;
|
private final StringFilterGroup notifyMe;
|
||||||
|
private final StringFilterGroup singleItemInformationPanel;
|
||||||
private final StringFilterGroup expandableMetadata;
|
private final StringFilterGroup expandableMetadata;
|
||||||
private final ByteArrayFilterGroup searchResultRecommendations;
|
private final ByteArrayFilterGroup searchResultRecommendations;
|
||||||
private final StringFilterGroup searchResultVideo;
|
private final StringFilterGroup searchResultVideo;
|
||||||
private final StringFilterGroup compactChannelBarInner;
|
private final StringFilterGroup compactChannelBarInner;
|
||||||
private final StringFilterGroup compactChannelBarInnerButton;
|
private final StringFilterGroup compactChannelBarInnerButton;
|
||||||
private final ByteArrayFilterGroup joinMembershipButton;
|
private final ByteArrayFilterGroup joinMembershipButton;
|
||||||
private final StringFilterGroup likeSubscribeGlow;
|
|
||||||
private final StringFilterGroup horizontalShelves;
|
private final StringFilterGroup horizontalShelves;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
public LayoutComponentsFilter() {
|
public LayoutComponentsFilter() {
|
||||||
exceptions.addPatterns(
|
exceptions.addPatterns(
|
||||||
"home_video_with_context",
|
"home_video_with_context",
|
||||||
@@ -74,13 +66,16 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
|
|
||||||
final var communityPosts = new StringFilterGroup(
|
final var communityPosts = new StringFilterGroup(
|
||||||
Settings.HIDE_COMMUNITY_POSTS,
|
Settings.HIDE_COMMUNITY_POSTS,
|
||||||
"post_base_wrapper",
|
"post_base_wrapper", // may be obsolete and no longer needed.
|
||||||
"text_post_root.eml",
|
"text_post_root.eml",
|
||||||
"images_post_root.eml",
|
"images_post_root.eml",
|
||||||
"images_post_slim.eml",
|
"images_post_slim.eml", // may be obsolete and no longer needed.
|
||||||
"images_post_root_slim.eml",
|
"images_post_root_slim.eml",
|
||||||
"text_post_root_slim.eml",
|
"text_post_root_slim.eml",
|
||||||
"post_base_wrapper_slim.eml"
|
"post_base_wrapper_slim.eml",
|
||||||
|
"poll_post_root.eml",
|
||||||
|
"videos_post_root.eml",
|
||||||
|
"post_shelf_slim.eml"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var communityGuidelines = new StringFilterGroup(
|
final var communityGuidelines = new StringFilterGroup(
|
||||||
@@ -106,7 +101,8 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
inFeedSurvey = new StringFilterGroup(
|
inFeedSurvey = new StringFilterGroup(
|
||||||
Settings.HIDE_FEED_SURVEY,
|
Settings.HIDE_FEED_SURVEY,
|
||||||
"in_feed_survey",
|
"in_feed_survey",
|
||||||
"slimline_survey"
|
"slimline_survey",
|
||||||
|
"feed_nudge"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var medicalPanel = new StringFilterGroup(
|
final var medicalPanel = new StringFilterGroup(
|
||||||
@@ -120,8 +116,12 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final var infoPanel = new StringFilterGroup(
|
final var infoPanel = new StringFilterGroup(
|
||||||
Settings.HIDE_HIDE_INFO_PANELS,
|
Settings.HIDE_INFO_PANELS,
|
||||||
"publisher_transparency_panel",
|
"publisher_transparency_panel"
|
||||||
|
);
|
||||||
|
|
||||||
|
singleItemInformationPanel = new StringFilterGroup(
|
||||||
|
Settings.HIDE_INFO_PANELS,
|
||||||
"single_item_information_panel"
|
"single_item_information_panel"
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -161,9 +161,9 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"inline_expander"
|
"inline_expander"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var channelBar = new StringFilterGroup(
|
final var compactChannelBar = new StringFilterGroup(
|
||||||
Settings.HIDE_CHANNEL_BAR,
|
Settings.HIDE_CHANNEL_BAR,
|
||||||
"channel_bar"
|
"compact_channel_bar"
|
||||||
);
|
);
|
||||||
|
|
||||||
final var relatedVideos = new StringFilterGroup(
|
final var relatedVideos = new StringFilterGroup(
|
||||||
@@ -194,11 +194,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"timed_reaction"
|
"timed_reaction"
|
||||||
);
|
);
|
||||||
|
|
||||||
searchResultShelfHeader = new StringFilterGroup(
|
|
||||||
Settings.HIDE_SEARCH_RESULT_SHELF_HEADER,
|
|
||||||
"shelf_header.eml"
|
|
||||||
);
|
|
||||||
|
|
||||||
notifyMe = new StringFilterGroup(
|
notifyMe = new StringFilterGroup(
|
||||||
Settings.HIDE_NOTIFY_ME_BUTTON,
|
Settings.HIDE_NOTIFY_ME_BUTTON,
|
||||||
"set_reminder_button"
|
"set_reminder_button"
|
||||||
@@ -219,10 +214,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
"sponsorships"
|
"sponsorships"
|
||||||
);
|
);
|
||||||
|
|
||||||
likeSubscribeGlow = new StringFilterGroup(
|
|
||||||
Settings.DISABLE_LIKE_SUBSCRIBE_GLOW,
|
|
||||||
"animated_button_border.eml"
|
|
||||||
);
|
|
||||||
|
|
||||||
final var channelWatermark = new StringFilterGroup(
|
final var channelWatermark = new StringFilterGroup(
|
||||||
Settings.HIDE_VIDEO_CHANNEL_WATERMARK,
|
Settings.HIDE_VIDEO_CHANNEL_WATERMARK,
|
||||||
@@ -256,8 +247,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
expandableMetadata,
|
expandableMetadata,
|
||||||
inFeedSurvey,
|
inFeedSurvey,
|
||||||
notifyMe,
|
notifyMe,
|
||||||
likeSubscribeGlow,
|
compactChannelBar,
|
||||||
channelBar,
|
|
||||||
communityPosts,
|
communityPosts,
|
||||||
paidPromotion,
|
paidPromotion,
|
||||||
searchResultVideo,
|
searchResultVideo,
|
||||||
@@ -271,6 +261,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
compactChannelBarInner,
|
compactChannelBarInner,
|
||||||
medicalPanel,
|
medicalPanel,
|
||||||
infoPanel,
|
infoPanel,
|
||||||
|
singleItemInformationPanel,
|
||||||
emergencyBox,
|
emergencyBox,
|
||||||
subscribersCommunityGuidelines,
|
subscribersCommunityGuidelines,
|
||||||
channelGuidelines,
|
channelGuidelines,
|
||||||
@@ -287,6 +278,19 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
@Override
|
@Override
|
||||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||||
|
// This identifier is used not only in players but also in search results:
|
||||||
|
// https://github.com/ReVanced/revanced-patches/issues/3245
|
||||||
|
// Until 2024, medical information panels such as Covid 19 also used this identifier and were shown in the search results.
|
||||||
|
// From 2025, the medical information panel is no longer shown in the search results.
|
||||||
|
// Therefore, this identifier does not filter when the search bar is activated.
|
||||||
|
if (matchedGroup == singleItemInformationPanel) {
|
||||||
|
if (PlayerType.getCurrent().isMaximizedOrFullscreen() || !NavigationBar.isSearchBarActive()) {
|
||||||
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (matchedGroup == searchResultVideo) {
|
if (matchedGroup == searchResultVideo) {
|
||||||
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
|
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
@@ -294,15 +298,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchedGroup == likeSubscribeGlow) {
|
|
||||||
if ((path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
|
|
||||||
&& path.contains(ANIMATED_VECTOR_TYPE_PATH)) {
|
|
||||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The groups are excluded from the filter due to the exceptions list below.
|
// The groups are excluded from the filter due to the exceptions list below.
|
||||||
// Filter them separately here.
|
// Filter them separately here.
|
||||||
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
|
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)
|
||||||
@@ -324,9 +319,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: This also hides the feed Shorts shelf header
|
|
||||||
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
|
|
||||||
|
|
||||||
if (matchedGroup == horizontalShelves) {
|
if (matchedGroup == horizontalShelves) {
|
||||||
if (contentIndex == 0 && hideShelves()) {
|
if (contentIndex == 0 && hideShelves()) {
|
||||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package app.revanced.extension.youtube.patches.components;
|
package app.revanced.extension.youtube.patches.components;
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
import app.revanced.extension.youtube.shared.PlayerType;
|
import app.revanced.extension.youtube.shared.PlayerType;
|
||||||
@@ -16,7 +13,6 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||||||
private final ByteArrayFilterGroup exception;
|
private final ByteArrayFilterGroup exception;
|
||||||
private final StringFilterGroup videoQualityMenuFooter;
|
private final StringFilterGroup videoQualityMenuFooter;
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
public PlayerFlyoutMenuItemsFilter() {
|
public PlayerFlyoutMenuItemsFilter() {
|
||||||
exception = new ByteArrayFilterGroup(
|
exception = new ByteArrayFilterGroup(
|
||||||
// Whitelist Quality menu item when "Hide Additional settings menu" is enabled
|
// Whitelist Quality menu item when "Hide Additional settings menu" is enabled
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ public final class ShortsFilter extends Filter {
|
|||||||
if (matchedGroup == suggestedAction) {
|
if (matchedGroup == suggestedAction) {
|
||||||
// Skip searching the buffer if all suggested actions are set to hidden.
|
// Skip searching the buffer if all suggested actions are set to hidden.
|
||||||
// This has a secondary effect of hiding all new un-identified actions
|
// This has a secondary effect of hiding all new un-identified actions
|
||||||
// under the assumption that the user wants all actions hidden.
|
// under the assumption that the user wants all suggestions hidden.
|
||||||
if (isEverySuggestedActionFilterEnabled()) {
|
if (isEverySuggestedActionFilterEnabled()) {
|
||||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||||
}
|
}
|
||||||
@@ -324,19 +324,22 @@ public final class ShortsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldHideShortsFeedItems() {
|
private static boolean shouldHideShortsFeedItems() {
|
||||||
|
// Known issue if hide home is on but at least one other hide is off:
|
||||||
|
//
|
||||||
|
// Shorts suggestions will load in the background if a video is opened and
|
||||||
|
// immediately minimized before any suggestions are loaded.
|
||||||
|
// In this state the player type will show minimized, which cannot
|
||||||
|
// distinguish between Shorts suggestions loading in the player and between
|
||||||
|
// scrolling thru search/home/subscription tabs while a player is minimized.
|
||||||
final boolean hideHome = Settings.HIDE_SHORTS_HOME.get();
|
final boolean hideHome = Settings.HIDE_SHORTS_HOME.get();
|
||||||
final boolean hideSubscriptions = Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
final boolean hideSubscriptions = Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
||||||
final boolean hideSearch = Settings.HIDE_SHORTS_SEARCH.get();
|
final boolean hideSearch = Settings.HIDE_SHORTS_SEARCH.get();
|
||||||
|
final boolean hideHistory = Settings.HIDE_SHORTS_HISTORY.get();
|
||||||
|
|
||||||
if (hideHome && hideSubscriptions && hideSearch) {
|
if (!hideHome && !hideSubscriptions && !hideSearch && !hideHistory) {
|
||||||
// Shorts suggestions can load in the background if a video is opened and
|
return false;
|
||||||
// then immediately minimized before any suggestions are loaded.
|
}
|
||||||
// In this state the player type will show minimized, which makes it not possible to
|
if (hideHome && hideSubscriptions && hideSearch && hideHistory) {
|
||||||
// distinguish between Shorts suggestions loading in the player and between
|
|
||||||
// scrolling thru search/home/subscription tabs while a player is minimized.
|
|
||||||
//
|
|
||||||
// To avoid this situation for users that never want to show Shorts (all hide Shorts options are enabled)
|
|
||||||
// then hide all Shorts everywhere including the Library history and Library playlists.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,24 +355,29 @@ public final class ShortsFilter extends Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Avoid checking navigation button status if all other Shorts should show.
|
// Avoid checking navigation button status if all other Shorts should show.
|
||||||
if (!hideHome && !hideSubscriptions) {
|
if (!hideHome && !hideSubscriptions && !hideHistory) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check navigation absolutely last since the check may block this thread.
|
||||||
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||||
if (selectedNavButton == null) {
|
if (selectedNavButton == null) {
|
||||||
return hideHome; // Unknown tab, treat the same as home.
|
return hideHome; // Unknown tab, treat the same as home.
|
||||||
}
|
}
|
||||||
if (selectedNavButton == NavigationButton.HOME) {
|
|
||||||
return hideHome;
|
return switch (selectedNavButton) {
|
||||||
}
|
case HOME, EXPLORE -> hideHome;
|
||||||
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
case SUBSCRIPTIONS -> hideSubscriptions;
|
||||||
return hideSubscriptions;
|
case LIBRARY -> hideHistory;
|
||||||
}
|
default -> false;
|
||||||
// User must be in the library tab. Don't hide the history or any playlists here.
|
};
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Only used if patching older than 19.03.
|
||||||
|
* This hook may be obsolete even for old versions
|
||||||
|
* as they now use a litho layout like newer versions.
|
||||||
|
*/
|
||||||
public static void hideShortsShelf(final View shortsShelfView) {
|
public static void hideShortsShelf(final View shortsShelfView) {
|
||||||
if (shouldHideShortsFeedItems()) {
|
if (shouldHideShortsFeedItems()) {
|
||||||
Utils.hideViewByLayoutParams(shortsShelfView);
|
Utils.hideViewByLayoutParams(shortsShelfView);
|
||||||
|
|||||||
@@ -32,6 +32,11 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
*/
|
*/
|
||||||
public static final float PLAYBACK_SPEED_MAXIMUM = 8;
|
public static final float PLAYBACK_SPEED_MAXIMUM = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tap and hold speed.
|
||||||
|
*/
|
||||||
|
private static final float TAP_AND_HOLD_SPEED;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom playback speeds.
|
* Custom playback speeds.
|
||||||
*/
|
*/
|
||||||
@@ -48,12 +53,27 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
private static String[] preferenceListEntries, preferenceListEntryValues;
|
private static String[] preferenceListEntries, preferenceListEntryValues;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get();
|
||||||
|
if (holdSpeed > 0 && holdSpeed <= PLAYBACK_SPEED_MAXIMUM) {
|
||||||
|
TAP_AND_HOLD_SPEED = holdSpeed;
|
||||||
|
} else {
|
||||||
|
showInvalidCustomSpeedToast();
|
||||||
|
Settings.SPEED_TAP_AND_HOLD.resetToDefault();
|
||||||
|
TAP_AND_HOLD_SPEED = Settings.SPEED_TAP_AND_HOLD.get();
|
||||||
|
}
|
||||||
|
|
||||||
loadCustomSpeeds();
|
loadCustomSpeeds();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void resetCustomSpeeds(@NonNull String toastMessage) {
|
/**
|
||||||
Utils.showToastLong(toastMessage);
|
* Injection point.
|
||||||
Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault();
|
*/
|
||||||
|
public static float tapAndHoldSpeed() {
|
||||||
|
return TAP_AND_HOLD_SPEED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showInvalidCustomSpeedToast() {
|
||||||
|
Utils.showToastLong(str("revanced_custom_playback_speeds_invalid", PLAYBACK_SPEED_MAXIMUM));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void loadCustomSpeeds() {
|
private static void loadCustomSpeeds() {
|
||||||
@@ -74,17 +94,18 @@ public class CustomPlaybackSpeedPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (speedFloat >= PLAYBACK_SPEED_MAXIMUM) {
|
if (speedFloat >= PLAYBACK_SPEED_MAXIMUM) {
|
||||||
resetCustomSpeeds(str("revanced_custom_playback_speeds_invalid", PLAYBACK_SPEED_MAXIMUM));
|
showInvalidCustomSpeedToast();
|
||||||
|
Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault();
|
||||||
loadCustomSpeeds();
|
loadCustomSpeeds();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
customPlaybackSpeeds[i] = speedFloat;
|
customPlaybackSpeeds[i++] = speedFloat;
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printInfo(() -> "parse error", ex);
|
Logger.printInfo(() -> "parse error", ex);
|
||||||
resetCustomSpeeds(str("revanced_custom_playback_speeds_parse_exception"));
|
Utils.showToastLong(str("revanced_custom_playback_speeds_parse_exception"));
|
||||||
|
Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault();
|
||||||
loadCustomSpeeds();
|
loadCustomSpeeds();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,19 @@ import android.content.res.Resources;
|
|||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.AnimatedVectorDrawable;
|
import android.graphics.drawable.AnimatedVectorDrawable;
|
||||||
|
|
||||||
|
import com.airbnb.lottie.LottieAnimationView;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -18,6 +26,8 @@ public final class SeekbarColorPatch {
|
|||||||
|
|
||||||
private static final boolean SEEKBAR_CUSTOM_COLOR_ENABLED = Settings.SEEKBAR_CUSTOM_COLOR.get();
|
private static final boolean SEEKBAR_CUSTOM_COLOR_ENABLED = Settings.SEEKBAR_CUSTOM_COLOR.get();
|
||||||
|
|
||||||
|
private static final boolean HIDE_SEEKBAR_THUMBNAIL_ENABLED = Settings.HIDE_SEEKBAR_THUMBNAIL.get();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default color of the litho seekbar.
|
* Default color of the litho seekbar.
|
||||||
* Differs slightly from the default custom seekbar color setting.
|
* Differs slightly from the default custom seekbar color setting.
|
||||||
@@ -25,14 +35,19 @@ public final class SeekbarColorPatch {
|
|||||||
private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000;
|
private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default colors of the gradient seekbar.
|
* Feed default colors of the gradient seekbar.
|
||||||
*/
|
*/
|
||||||
private static final int[] ORIGINAL_SEEKBAR_GRADIENT_COLORS = { 0xFFFF0033, 0xFFFF2791 };
|
private static final int[] FEED_ORIGINAL_SEEKBAR_GRADIENT_COLORS = { 0xFFFF0033, 0xFFFF2791 };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default positions of the gradient seekbar.
|
* Feed default positions of the gradient seekbar.
|
||||||
*/
|
*/
|
||||||
private static final float[] ORIGINAL_SEEKBAR_GRADIENT_POSITIONS = { 0.8f, 1.0f };
|
private static final float[] FEED_ORIGINAL_SEEKBAR_GRADIENT_POSITIONS = { 0.8f, 1.0f };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Empty seekbar gradient, if hide seekbar in feed is enabled.
|
||||||
|
*/
|
||||||
|
private static final int[] HIDDEN_SEEKBAR_GRADIENT_COLORS = { 0x0, 0x0 };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default YouTube seekbar color brightness.
|
* Default YouTube seekbar color brightness.
|
||||||
@@ -41,16 +56,21 @@ public final class SeekbarColorPatch {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If {@link Settings#SEEKBAR_CUSTOM_COLOR} is enabled,
|
* If {@link Settings#SEEKBAR_CUSTOM_COLOR} is enabled,
|
||||||
* this is the color value of {@link Settings#SEEKBAR_CUSTOM_COLOR_VALUE}.
|
* this is the color value of {@link Settings#SEEKBAR_CUSTOM_COLOR_PRIMARY}.
|
||||||
* Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}.
|
* Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}.
|
||||||
*/
|
*/
|
||||||
private static int seekbarColor = ORIGINAL_SEEKBAR_COLOR;
|
private static int customSeekbarColor = ORIGINAL_SEEKBAR_COLOR;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom seekbar hue, saturation, and brightness values.
|
* Custom seekbar hue, saturation, and brightness values.
|
||||||
*/
|
*/
|
||||||
private static final float[] customSeekbarColorHSV = new float[3];
|
private static final float[] customSeekbarColorHSV = new float[3];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom seekbar color, used for linear gradient replacements.
|
||||||
|
*/
|
||||||
|
private static final int[] customSeekbarColorGradient = new int[2];
|
||||||
|
|
||||||
static {
|
static {
|
||||||
float[] hsv = new float[3];
|
float[] hsv = new float[3];
|
||||||
Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv);
|
Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv);
|
||||||
@@ -63,37 +83,22 @@ public final class SeekbarColorPatch {
|
|||||||
|
|
||||||
private static void loadCustomSeekbarColor() {
|
private static void loadCustomSeekbarColor() {
|
||||||
try {
|
try {
|
||||||
seekbarColor = Color.parseColor(Settings.SEEKBAR_CUSTOM_COLOR_VALUE.get());
|
customSeekbarColor = Color.parseColor(Settings.SEEKBAR_CUSTOM_COLOR_PRIMARY.get());
|
||||||
Color.colorToHSV(seekbarColor, customSeekbarColorHSV);
|
Color.colorToHSV(customSeekbarColor, customSeekbarColorHSV);
|
||||||
|
|
||||||
|
customSeekbarColorGradient[0] = customSeekbarColor;
|
||||||
|
customSeekbarColorGradient[1] = Color.parseColor(Settings.SEEKBAR_CUSTOM_COLOR_ACCENT.get());
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Utils.showToastShort(str("revanced_seekbar_custom_color_invalid"));
|
Utils.showToastShort(str("revanced_seekbar_custom_color_invalid"));
|
||||||
Settings.SEEKBAR_CUSTOM_COLOR_VALUE.resetToDefault();
|
Settings.SEEKBAR_CUSTOM_COLOR_PRIMARY.resetToDefault();
|
||||||
|
Settings.SEEKBAR_CUSTOM_COLOR_ACCENT.resetToDefault();
|
||||||
|
|
||||||
loadCustomSeekbarColor();
|
loadCustomSeekbarColor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getSeekbarColor() {
|
public static int getSeekbarColor() {
|
||||||
return seekbarColor;
|
return customSeekbarColor;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point
|
|
||||||
*/
|
|
||||||
public static boolean playerSeekbarGradientEnabled(boolean original) {
|
|
||||||
if (SEEKBAR_CUSTOM_COLOR_ENABLED) return false;
|
|
||||||
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point
|
|
||||||
*/
|
|
||||||
public static boolean useLotteLaunchSplashScreen(boolean original) {
|
|
||||||
Logger.printDebug(() -> "useLotteLaunchSplashScreen original: " + original);
|
|
||||||
|
|
||||||
if (SEEKBAR_CUSTOM_COLOR_ENABLED) return false;
|
|
||||||
|
|
||||||
return original;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int colorChannelTo3Bits(int channel8Bits) {
|
private static int colorChannelTo3Bits(int channel8Bits) {
|
||||||
@@ -119,6 +124,17 @@ public final class SeekbarColorPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point
|
* Injection point
|
||||||
*/
|
*/
|
||||||
|
public static boolean useLotteLaunchSplashScreen(boolean original) {
|
||||||
|
// This method is only used for development purposes to force the old style launch screen.
|
||||||
|
// Forcing this off on some devices can cause unexplained startup crashes,
|
||||||
|
// where the lottie animation is still used even though this condition appears to bypass it.
|
||||||
|
return original; // false = drawable style, true = lottie style.
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Old drawable style launch screen.
|
||||||
|
*/
|
||||||
public static void setSplashAnimationDrawableTheme(AnimatedVectorDrawable vectorDrawable) {
|
public static void setSplashAnimationDrawableTheme(AnimatedVectorDrawable vectorDrawable) {
|
||||||
// Alternatively a ColorMatrixColorFilter can be used to change the color of the drawable
|
// Alternatively a ColorMatrixColorFilter can be used to change the color of the drawable
|
||||||
// without using any styles, but a color filter cannot selectively change the seekbar
|
// without using any styles, but a color filter cannot selectively change the seekbar
|
||||||
@@ -126,7 +142,9 @@ public final class SeekbarColorPatch {
|
|||||||
// Even if the seekbar color xml value is changed to a completely different color (such as green),
|
// Even if the seekbar color xml value is changed to a completely different color (such as green),
|
||||||
// a color filter still cannot be selectively applied when the drawable has more than 1 color.
|
// a color filter still cannot be selectively applied when the drawable has more than 1 color.
|
||||||
try {
|
try {
|
||||||
String seekbarStyle = get9BitStyleIdentifier(seekbarColor);
|
// Must set the color even if custom seekbar is off,
|
||||||
|
// because the xml color was replaced with a themed value.
|
||||||
|
String seekbarStyle = get9BitStyleIdentifier(customSeekbarColor);
|
||||||
Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle);
|
Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle);
|
||||||
|
|
||||||
final int styleIdentifierDefault = Utils.getResourceIdentifier(
|
final int styleIdentifierDefault = Utils.getResourceIdentifier(
|
||||||
@@ -146,6 +164,84 @@ public final class SeekbarColorPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Modern Lottie style animation.
|
||||||
|
*/
|
||||||
|
public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) {
|
||||||
|
try {
|
||||||
|
if (!SEEKBAR_CUSTOM_COLOR_ENABLED) {
|
||||||
|
view.patch_setAnimation(resourceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//noinspection ConstantConditions
|
||||||
|
if (false) { // Set true to force slow animation for development.
|
||||||
|
final int longAnimation = Utils.getResourceIdentifier(
|
||||||
|
Utils.isDarkModeEnabled(Utils.getContext())
|
||||||
|
? "startup_animation_5s_30fps_dark"
|
||||||
|
: "startup_animation_5s_30fps_light",
|
||||||
|
"raw");
|
||||||
|
if (longAnimation != 0) {
|
||||||
|
resourceId = longAnimation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must specify primary key name otherwise the morphing YT logo color is also changed.
|
||||||
|
String originalKey = "\"k\":";
|
||||||
|
String originalPrimary = originalKey + "[1,0,0.2,1]";
|
||||||
|
String originalAccent = originalKey + "[1,0.152941176471,0.56862745098,1]";
|
||||||
|
|
||||||
|
String replacementPrimary = originalKey + getColorStringArray(customSeekbarColor);
|
||||||
|
String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]);
|
||||||
|
|
||||||
|
String json = loadRawResourceAsString(resourceId);
|
||||||
|
if (json == null) {
|
||||||
|
return; // Should never happen.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) {
|
||||||
|
String jsonFinal = json;
|
||||||
|
Logger.printException(() -> "Could not replace launch animation colors: " + jsonFinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Replacing Lottie animation JSON");
|
||||||
|
json = json.replace(originalPrimary, replacementPrimary);
|
||||||
|
json = json.replace(originalAccent, replacementAccent);
|
||||||
|
|
||||||
|
// cacheKey is not needed since the animation will not be reused.
|
||||||
|
view.patch_setAnimation(new ByteArrayInputStream(json.getBytes()), null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "setSplashAnimationLottie failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getColorStringArray(int color) {
|
||||||
|
return Arrays.toString(new double[]{
|
||||||
|
Color.red(color) / 255.0,
|
||||||
|
Color.green(color) / 255.0,
|
||||||
|
Color.blue(color) / 255.0,
|
||||||
|
Color.alpha(color) / 255.0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String loadRawResourceAsString(int resourceId) {
|
||||||
|
try (InputStream inputStream = Utils.getContext().getResources().openRawResource(resourceId);
|
||||||
|
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
|
||||||
|
return scanner.next();
|
||||||
|
} catch (IOException e) {
|
||||||
|
Logger.printException(() -> "Could not load resource: " + resourceId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean showWatchHistoryProgressDrawable(boolean original) {
|
||||||
|
return !HIDE_SEEKBAR_THUMBNAIL_ENABLED && original;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
*
|
||||||
@@ -156,35 +252,61 @@ public final class SeekbarColorPatch {
|
|||||||
*/
|
*/
|
||||||
public static int getLithoColor(int colorValue) {
|
public static int getLithoColor(int colorValue) {
|
||||||
if (colorValue == ORIGINAL_SEEKBAR_COLOR) {
|
if (colorValue == ORIGINAL_SEEKBAR_COLOR) {
|
||||||
if (Settings.HIDE_SEEKBAR_THUMBNAIL.get()) {
|
if (HIDE_SEEKBAR_THUMBNAIL_ENABLED) {
|
||||||
return 0x00000000;
|
return 0x0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSeekbarColorValue(ORIGINAL_SEEKBAR_COLOR);
|
return customSeekbarColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
return colorValue;
|
return colorValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String colorArrayToHex(int[] colors) {
|
||||||
|
final int length = colors.length;
|
||||||
|
StringBuilder builder = new StringBuilder(length * 12);
|
||||||
|
builder.append("[");
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (int color : colors) {
|
||||||
|
builder.append(String.format("#%X", color));
|
||||||
|
if (++i < length) {
|
||||||
|
builder.append(", ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("]");
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void setLinearGradient(int[] colors, float[] positions) {
|
public static int[] getPlayerLinearGradient(int[] original) {
|
||||||
final boolean hideSeekbar = Settings.HIDE_SEEKBAR_THUMBNAIL.get();
|
return SEEKBAR_CUSTOM_COLOR_ENABLED
|
||||||
|
? customSeekbarColorGradient
|
||||||
|
: original;
|
||||||
|
}
|
||||||
|
|
||||||
if (SEEKBAR_CUSTOM_COLOR_ENABLED || hideSeekbar) {
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static int[] getLithoLinearGradient(int[] colors, float[] positions) {
|
||||||
|
if (SEEKBAR_CUSTOM_COLOR_ENABLED || HIDE_SEEKBAR_THUMBNAIL_ENABLED) {
|
||||||
// Most litho usage of linear gradients is hooked here,
|
// Most litho usage of linear gradients is hooked here,
|
||||||
// so must only change if the values are those for the seekbar.
|
// so must only change if the values are those for the seekbar.
|
||||||
if (Arrays.equals(ORIGINAL_SEEKBAR_GRADIENT_COLORS, colors)
|
if ((Arrays.equals(FEED_ORIGINAL_SEEKBAR_GRADIENT_COLORS, colors)
|
||||||
&& Arrays.equals(ORIGINAL_SEEKBAR_GRADIENT_POSITIONS, positions)) {
|
&& Arrays.equals(FEED_ORIGINAL_SEEKBAR_GRADIENT_POSITIONS, positions))) {
|
||||||
Arrays.fill(colors, hideSeekbar
|
return HIDE_SEEKBAR_THUMBNAIL_ENABLED
|
||||||
? 0x00000000
|
? HIDDEN_SEEKBAR_GRADIENT_COLORS
|
||||||
: seekbarColor);
|
: customSeekbarColorGradient;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "Ignoring gradient colors: " + Arrays.toString(colors)
|
Logger.printDebug(() -> "Ignoring gradient colors: " + colorArrayToHex(colors)
|
||||||
+ " positions: " + Arrays.toString(positions));
|
+ " positions: " + Arrays.toString(positions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return colors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -198,7 +320,7 @@ public final class SeekbarColorPatch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return colorValue == ORIGINAL_SEEKBAR_COLOR
|
return colorValue == ORIGINAL_SEEKBAR_COLOR
|
||||||
? getSeekbarColorValue(ORIGINAL_SEEKBAR_COLOR)
|
? customSeekbarColor
|
||||||
: colorValue;
|
: colorValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,11 +330,9 @@ public final class SeekbarColorPatch {
|
|||||||
* Overrides color used for the video player seekbar.
|
* Overrides color used for the video player seekbar.
|
||||||
*/
|
*/
|
||||||
public static int getVideoPlayerSeekbarColor(int originalColor) {
|
public static int getVideoPlayerSeekbarColor(int originalColor) {
|
||||||
if (!SEEKBAR_CUSTOM_COLOR_ENABLED) {
|
return SEEKBAR_CUSTOM_COLOR_ENABLED
|
||||||
return originalColor;
|
? getSeekbarColorValue(originalColor)
|
||||||
}
|
: originalColor;
|
||||||
|
|
||||||
return getSeekbarColorValue(originalColor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -221,10 +341,6 @@ public final class SeekbarColorPatch {
|
|||||||
*/
|
*/
|
||||||
private static int getSeekbarColorValue(int originalColor) {
|
private static int getSeekbarColorValue(int originalColor) {
|
||||||
try {
|
try {
|
||||||
if (!SEEKBAR_CUSTOM_COLOR_ENABLED || originalColor == seekbarColor) {
|
|
||||||
return originalColor; // nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
final int alphaDifference = Color.alpha(originalColor) - Color.alpha(ORIGINAL_SEEKBAR_COLOR);
|
final int alphaDifference = Color.alpha(originalColor) - Color.alpha(ORIGINAL_SEEKBAR_COLOR);
|
||||||
|
|
||||||
// The seekbar uses the same color but different brightness for different situations.
|
// The seekbar uses the same color but different brightness for different situations.
|
||||||
@@ -237,7 +353,7 @@ public final class SeekbarColorPatch {
|
|||||||
hsv[1] = customSeekbarColorHSV[1];
|
hsv[1] = customSeekbarColorHSV[1];
|
||||||
hsv[2] = clamp(customSeekbarColorHSV[2] + brightnessDifference, 0, 1);
|
hsv[2] = clamp(customSeekbarColorHSV[2] + brightnessDifference, 0, 1);
|
||||||
|
|
||||||
final int replacementAlpha = clamp(Color.alpha(seekbarColor) + alphaDifference, 0, 255);
|
final int replacementAlpha = clamp(Color.alpha(customSeekbarColor) + alphaDifference, 0, 255);
|
||||||
final int replacementColor = Color.HSVToColor(replacementAlpha, hsv);
|
final int replacementColor = Color.HSVToColor(replacementAlpha, hsv);
|
||||||
Logger.printDebug(() -> String.format("Original color: #%08X replacement color: #%08X",
|
Logger.printDebug(() -> String.format("Original color: #%08X replacement color: #%08X",
|
||||||
originalColor, replacementColor));
|
originalColor, replacementColor));
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class ThemePatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static boolean gradientLoadingScreenEnabled() {
|
public static boolean gradientLoadingScreenEnabled(boolean original) {
|
||||||
return GRADIENT_LOADING_SCREEN_ENABLED;
|
return GRADIENT_LOADING_SCREEN_ENABLED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,6 +234,12 @@ public class ReturnYouTubeDislike {
|
|||||||
// example video: https://www.youtube.com/watch?v=UnrU5vxCHxw
|
// example video: https://www.youtube.com/watch?v=UnrU5vxCHxw
|
||||||
// RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw
|
// RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw
|
||||||
//
|
//
|
||||||
|
if (!Settings.RYD_ESTIMATED_LIKE.get()) {
|
||||||
|
// Change the "Likes" string to show that likes and dislikes are hidden.
|
||||||
|
String hiddenMessageString = str("revanced_ryd_video_likes_hidden_by_video_owner");
|
||||||
|
return newSpanUsingStylingOfAnotherSpan(oldSpannable, hiddenMessageString);
|
||||||
|
}
|
||||||
|
|
||||||
Logger.printDebug(() -> "Using estimated likes");
|
Logger.printDebug(() -> "Using estimated likes");
|
||||||
oldLikes = formatDislikeCount(voteData.getLikeCount());
|
oldLikes = formatDislikeCount(voteData.getLikeCount());
|
||||||
}
|
}
|
||||||
@@ -346,56 +352,49 @@ public class ReturnYouTubeDislike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String formatDislikeCount(long dislikeCount) {
|
private static String formatDislikeCount(long dislikeCount) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize
|
||||||
synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize
|
if (dislikeCountFormatter == null) {
|
||||||
if (dislikeCountFormatter == null) {
|
Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale;
|
||||||
Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale;
|
dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT);
|
||||||
dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT);
|
|
||||||
|
|
||||||
// YouTube disregards locale specific number characters
|
// YouTube disregards locale specific number characters
|
||||||
// and instead shows english number characters everywhere.
|
// and instead shows english number characters everywhere.
|
||||||
// To use the same behavior, override the digit characters to use English
|
// To use the same behavior, override the digit characters to use English
|
||||||
// so languages such as Arabic will show "1.234" instead of the native "۱,۲۳٤"
|
// so languages such as Arabic will show "1.234" instead of the native "۱,۲۳٤"
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
|
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
|
||||||
symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings());
|
symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings());
|
||||||
dislikeCountFormatter.setDecimalFormatSymbols(symbols);
|
dislikeCountFormatter.setDecimalFormatSymbols(symbols);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return dislikeCountFormatter.format(dislikeCount);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Will never be reached, as the oldest supported YouTube app requires Android N or greater.
|
return dislikeCountFormatter.format(dislikeCount);
|
||||||
return String.valueOf(dislikeCount);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatDislikePercentage(float dislikePercentage) {
|
private static String formatDislikePercentage(float dislikePercentage) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
synchronized (ReturnYouTubeDislike.class) { // Number formatter is not thread safe, must synchronize.
|
||||||
synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize
|
if (dislikePercentageFormatter == null) {
|
||||||
if (dislikePercentageFormatter == null) {
|
Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale;
|
||||||
Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale;
|
dislikePercentageFormatter = NumberFormat.getPercentInstance(locale);
|
||||||
dislikePercentageFormatter = NumberFormat.getPercentInstance(locale);
|
|
||||||
|
|
||||||
// Want to set the digit strings, and the simplest way is to cast to the implementation NumberFormat returns.
|
// Want to set the digit strings, and the simplest way is to cast to the implementation NumberFormat returns.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
||||||
&& dislikePercentageFormatter instanceof DecimalFormat) {
|
&& dislikePercentageFormatter instanceof DecimalFormat) {
|
||||||
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
|
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
|
||||||
symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings());
|
symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings());
|
||||||
((DecimalFormat) dislikePercentageFormatter).setDecimalFormatSymbols(symbols);
|
((DecimalFormat) dislikePercentageFormatter).setDecimalFormatSymbols(symbols);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (dislikePercentage >= 0.01) { // at least 1%
|
|
||||||
dislikePercentageFormatter.setMaximumFractionDigits(0); // show only whole percentage points
|
|
||||||
} else {
|
|
||||||
dislikePercentageFormatter.setMaximumFractionDigits(1); // show up to 1 digit precision
|
|
||||||
}
|
|
||||||
return dislikePercentageFormatter.format(dislikePercentage);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Will never be reached, as the oldest supported YouTube app requires Android N or greater.
|
if (dislikePercentage >= 0.01) { // at least 1%
|
||||||
return String.valueOf((int) (dislikePercentage * 100));
|
dislikePercentageFormatter.setMaximumFractionDigits(0); // show only whole percentage points
|
||||||
|
} else {
|
||||||
|
dislikePercentageFormatter.setMaximumFractionDigits(1); // show up to 1 digit precision
|
||||||
|
}
|
||||||
|
|
||||||
|
return dislikePercentageFormatter.format(dislikePercentage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -403,15 +402,13 @@ public class ReturnYouTubeDislike {
|
|||||||
Objects.requireNonNull(videoId);
|
Objects.requireNonNull(videoId);
|
||||||
synchronized (fetchCache) {
|
synchronized (fetchCache) {
|
||||||
// Remove any expired entries.
|
// Remove any expired entries.
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
final long now = System.currentTimeMillis();
|
||||||
final long now = System.currentTimeMillis();
|
fetchCache.values().removeIf(value -> {
|
||||||
fetchCache.values().removeIf(value -> {
|
final boolean expired = value.isExpired(now);
|
||||||
final boolean expired = value.isExpired(now);
|
if (expired)
|
||||||
if (expired)
|
Logger.printDebug(() -> "Removing expired fetch: " + value.videoId);
|
||||||
Logger.printDebug(() -> "Removing expired fetch: " + value.videoId);
|
return expired;
|
||||||
return expired;
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ReturnYouTubeDislike fetch = fetchCache.get(videoId);
|
ReturnYouTubeDislike fetch = fetchCache.get(videoId);
|
||||||
if (fetch == null) {
|
if (fetch == null) {
|
||||||
@@ -551,6 +548,15 @@ public class ReturnYouTubeDislike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (spanIsForLikes) {
|
if (spanIsForLikes) {
|
||||||
|
if (!Utils.containsNumber(original)) {
|
||||||
|
if (!Settings.RYD_ESTIMATED_LIKE.get()) {
|
||||||
|
Logger.printDebug(() -> "Likes are hidden");
|
||||||
|
return original;
|
||||||
|
} else {
|
||||||
|
Logger.printDebug(() -> "Using estimated likes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Scrolling Shorts does not cause the Spans to be reloaded,
|
// Scrolling Shorts does not cause the Spans to be reloaded,
|
||||||
// so there is no need to cache the likes for this situations.
|
// so there is no need to cache the likes for this situations.
|
||||||
Logger.printDebug(() -> "Creating likes span for: " + votingData.videoId);
|
Logger.printDebug(() -> "Creating likes span for: " + votingData.videoId);
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
package app.revanced.extension.youtube.settings;
|
package app.revanced.extension.youtube.settings;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import app.revanced.extension.shared.Logger;
|
import android.widget.Toolbar;
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
|
||||||
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
|
||||||
import app.revanced.extension.youtube.settings.preference.ReturnYouTubeDislikePreferenceFragment;
|
|
||||||
import app.revanced.extension.youtube.settings.preference.SponsorBlockPreferenceFragment;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static app.revanced.extension.shared.Utils.getChildView;
|
import app.revanced.extension.shared.Logger;
|
||||||
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.AppLanguage;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.extension.youtube.ThemeHelper;
|
||||||
|
import app.revanced.extension.youtube.patches.VersionCheckPatch;
|
||||||
|
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||||
|
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
|
||||||
|
import app.revanced.extension.youtube.settings.preference.ReturnYouTubeDislikePreferenceFragment;
|
||||||
|
import app.revanced.extension.youtube.settings.preference.SponsorBlockPreferenceFragment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks LicenseActivity.
|
* Hooks LicenseActivity.
|
||||||
@@ -25,6 +32,50 @@ import static app.revanced.extension.shared.Utils.getResourceIdentifier;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class LicenseActivityHook {
|
public class LicenseActivityHook {
|
||||||
|
|
||||||
|
private static ViewGroup.LayoutParams toolbarLayoutParams;
|
||||||
|
|
||||||
|
public static void setToolbarLayoutParams(Toolbar toolbar) {
|
||||||
|
if (toolbarLayoutParams != null) {
|
||||||
|
toolbar.setLayoutParams(toolbarLayoutParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Overrides the ReVanced settings language.
|
||||||
|
*/
|
||||||
|
public static Context getAttachBaseContext(Context original) {
|
||||||
|
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
|
||||||
|
if (language == AppLanguage.DEFAULT) {
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Utils.getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
|
public static boolean useCairoSettingsFragment(boolean original) {
|
||||||
|
// Early targets have layout issues and it's better to always force off.
|
||||||
|
if (!VersionCheckPatch.IS_19_34_OR_GREATER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (Settings.RESTORE_OLD_SETTINGS_MENUS.get()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Spoofing can cause half broken settings menus of old and new settings.
|
||||||
|
if (SpoofAppVersionPatch.isSpoofingToLessThan("19.35.36")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// On the first launch of a clean install, forcing the cairo menu can give a
|
||||||
|
// half broken appearance because all the preference icons may not be available yet.
|
||||||
|
// 19.34+ cairo settings are always on, so it doesn't need to be forced anyway.
|
||||||
|
// Cairo setting will show on the next launch of the app.
|
||||||
|
return original;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -33,9 +84,8 @@ public class LicenseActivityHook {
|
|||||||
public static void initialize(Activity licenseActivity) {
|
public static void initialize(Activity licenseActivity) {
|
||||||
try {
|
try {
|
||||||
ThemeHelper.setActivityTheme(licenseActivity);
|
ThemeHelper.setActivityTheme(licenseActivity);
|
||||||
licenseActivity.setContentView(
|
licenseActivity.setContentView(getResourceIdentifier(
|
||||||
getResourceIdentifier("revanced_settings_with_toolbar", "layout"));
|
"revanced_settings_with_toolbar", "layout"));
|
||||||
setBackButton(licenseActivity);
|
|
||||||
|
|
||||||
PreferenceFragment fragment;
|
PreferenceFragment fragment;
|
||||||
String toolbarTitleResourceName;
|
String toolbarTitleResourceName;
|
||||||
@@ -58,7 +108,7 @@ public class LicenseActivityHook {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setToolbarTitle(licenseActivity, toolbarTitleResourceName);
|
createToolbar(licenseActivity, toolbarTitleResourceName);
|
||||||
|
|
||||||
//noinspection deprecation
|
//noinspection deprecation
|
||||||
licenseActivity.getFragmentManager()
|
licenseActivity.getFragmentManager()
|
||||||
@@ -70,28 +120,34 @@ public class LicenseActivityHook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setToolbarTitle(Activity activity, String toolbarTitleResourceName) {
|
|
||||||
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
|
||||||
TextView toolbarTextView = Objects.requireNonNull(getChildView(toolbar, false,
|
|
||||||
view -> view instanceof TextView));
|
|
||||||
toolbarTextView.setText(getResourceIdentifier(toolbarTitleResourceName, "string"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("UseCompatLoadingForDrawables")
|
@SuppressLint("UseCompatLoadingForDrawables")
|
||||||
private static void setBackButton(Activity activity) {
|
private static void createToolbar(Activity activity, String toolbarTitleResourceName) {
|
||||||
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
// Replace dummy placeholder toolbar.
|
||||||
ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, false,
|
// This is required to fix submenu title alignment issue with Android ASOP 15+
|
||||||
view -> view instanceof ImageButton));
|
ViewGroup toolBarParent = activity.findViewById(
|
||||||
imageButton.setImageDrawable(ReVancedPreferenceFragment.getBackButtonDrawable());
|
getResourceIdentifier("revanced_toolbar_parent", "id"));
|
||||||
imageButton.setOnClickListener(view -> activity.onBackPressed());
|
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent,"revanced_toolbar");
|
||||||
}
|
toolbarLayoutParams = dummyToolbar.getLayoutParams();
|
||||||
|
toolBarParent.removeView(dummyToolbar);
|
||||||
|
|
||||||
private static int getToolbarResourceId() {
|
Toolbar toolbar = new Toolbar(toolBarParent.getContext());
|
||||||
final int toolbarResourceId = getResourceIdentifier("revanced_toolbar", "id");
|
toolbar.setBackgroundColor(ThemeHelper.getToolbarBackgroundColor());
|
||||||
if (toolbarResourceId == 0) {
|
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
|
||||||
throw new IllegalStateException("Could not find back button resource");
|
toolbar.setNavigationOnClickListener(view -> activity.onBackPressed());
|
||||||
|
toolbar.setTitle(getResourceIdentifier(toolbarTitleResourceName, "string"));
|
||||||
|
|
||||||
|
final int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16,
|
||||||
|
Utils.getContext().getResources().getDisplayMetrics());
|
||||||
|
toolbar.setTitleMarginStart(margin);
|
||||||
|
toolbar.setTitleMarginEnd(margin);
|
||||||
|
TextView toolbarTextView = Utils.getChildView(toolbar, false,
|
||||||
|
view -> view instanceof TextView);
|
||||||
|
if (toolbarTextView != null) {
|
||||||
|
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor());
|
||||||
}
|
}
|
||||||
return toolbarResourceId;
|
setToolbarLayoutParams(toolbar);
|
||||||
|
|
||||||
|
toolBarParent.addView(toolbar, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import static app.revanced.extension.shared.settings.Setting.migrateFromOldPrefe
|
|||||||
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
import static app.revanced.extension.shared.settings.Setting.migrateOldSettingToNew;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parent;
|
import static app.revanced.extension.shared.settings.Setting.parent;
|
||||||
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
import static app.revanced.extension.shared.settings.Setting.parentsAny;
|
||||||
|
import static app.revanced.extension.youtube.patches.ChangeFormFactorPatch.FormFactor;
|
||||||
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
|
import static app.revanced.extension.youtube.patches.ChangeStartPagePatch.StartPage;
|
||||||
|
import static app.revanced.extension.youtube.patches.ExitFullscreenPatch.FullscreenMode;
|
||||||
|
import static app.revanced.extension.youtube.patches.ForceOriginalAudioPatch.ForceOriginalAudioAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHideExpandCloseAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerHorizontalDragAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType;
|
||||||
@@ -16,7 +19,7 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerT
|
|||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_2;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_2;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_3;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_3;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_4;
|
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.MODERN_4;
|
||||||
import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerType.PHONE;
|
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
|
||||||
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
|
||||||
import static app.revanced.extension.youtube.patches.VersionCheckPatch.IS_19_17_OR_GREATER;
|
import static app.revanced.extension.youtube.patches.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.IGNORE;
|
||||||
@@ -24,6 +27,8 @@ import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehavi
|
|||||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
|
||||||
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
|
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.settings.BaseSettings;
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
import app.revanced.extension.shared.settings.BooleanSetting;
|
import app.revanced.extension.shared.settings.BooleanSetting;
|
||||||
@@ -42,19 +47,24 @@ import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
|
|||||||
|
|
||||||
public class Settings extends BaseSettings {
|
public class Settings extends BaseSettings {
|
||||||
// Video
|
// Video
|
||||||
|
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
|
||||||
public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
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 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_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
|
||||||
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
|
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
|
||||||
// Speed
|
// 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);
|
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE);
|
||||||
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
|
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
|
||||||
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
|
||||||
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
|
||||||
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
|
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true);
|
||||||
|
// Audio
|
||||||
|
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability());
|
||||||
|
|
||||||
// Ads
|
// Ads
|
||||||
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
|
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
|
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
|
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
|
||||||
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
|
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
|
||||||
@@ -91,7 +101,6 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
||||||
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATIONS = new BooleanSetting("revanced_hide_search_result_recommendations", TRUE);
|
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATIONS = new BooleanSetting("revanced_hide_search_result_recommendations", TRUE);
|
||||||
public static final BooleanSetting HIDE_SEARCH_RESULT_SHELF_HEADER = new BooleanSetting("revanced_hide_search_result_shelf_header", FALSE);
|
|
||||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||||
// Alternative thumbnails
|
// Alternative thumbnails
|
||||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
||||||
@@ -115,9 +124,9 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
|
||||||
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
|
||||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||||
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
|
|
||||||
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
||||||
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE, true);
|
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE, true);
|
||||||
|
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
|
||||||
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
|
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
||||||
@@ -127,7 +136,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
|
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
|
||||||
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
|
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
|
||||||
public static final BooleanSetting HIDE_HIDE_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_channel_guidelines", TRUE);
|
public static final BooleanSetting HIDE_HIDE_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_channel_guidelines", TRUE);
|
||||||
public static final BooleanSetting HIDE_HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
|
public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
|
||||||
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
|
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
|
||||||
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
|
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
||||||
@@ -137,18 +146,19 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
|
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
|
||||||
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
|
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
|
||||||
public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
|
public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
|
||||||
|
public static final BooleanSetting OPEN_VIDEOS_FULLSCREEN_PORTRAIT = new BooleanSetting("revanced_open_videos_fullscreen_portrait", FALSE);
|
||||||
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
|
public static final BooleanSetting PLAYBACK_SPEED_DIALOG_BUTTON = new BooleanSetting("revanced_playback_speed_dialog_button", FALSE);
|
||||||
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
|
|
||||||
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
|
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity", 100, true);
|
||||||
|
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
|
||||||
// Miniplayer
|
// Miniplayer
|
||||||
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.ORIGINAL, true);
|
public static final EnumSetting<MiniplayerType> MINIPLAYER_TYPE = new EnumSetting<>("revanced_miniplayer_type", MiniplayerType.DEFAULT, true);
|
||||||
private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
|
private static final Availability MINIPLAYER_ANY_MODERN = MINIPLAYER_TYPE.availability(MODERN_1, MODERN_2, MODERN_3, MODERN_4);
|
||||||
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN);
|
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, MINIPLAYER_ANY_MODERN);
|
||||||
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN);
|
public static final BooleanSetting MINIPLAYER_DRAG_AND_DROP = new BooleanSetting("revanced_miniplayer_drag_and_drop", TRUE, true, MINIPLAYER_ANY_MODERN);
|
||||||
public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerHorizontalDragAvailability());
|
public static final BooleanSetting MINIPLAYER_HORIZONTAL_DRAG = new BooleanSetting("revanced_miniplayer_horizontal_drag", FALSE, true, new MiniplayerHorizontalDragAvailability());
|
||||||
public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true, new MiniplayerHideExpandCloseAvailability());
|
public static final BooleanSetting MINIPLAYER_HIDE_EXPAND_CLOSE = new BooleanSetting("revanced_miniplayer_hide_expand_close", FALSE, true, new MiniplayerHideExpandCloseAvailability());
|
||||||
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3));
|
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1, MODERN_3));
|
||||||
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", FALSE, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
||||||
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
|
public static final BooleanSetting MINIPLAYER_ROUNDED_CORNERS = new BooleanSetting("revanced_miniplayer_rounded_corners", TRUE, true, MINIPLAYER_ANY_MODERN);
|
||||||
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
|
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, MINIPLAYER_ANY_MODERN);
|
||||||
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, MINIPLAYER_TYPE.availability(MODERN_1));
|
||||||
@@ -158,6 +168,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||||
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||||
// Comments
|
// Comments
|
||||||
|
public static final BooleanSetting HIDE_COMMENTS_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_chat_summary", FALSE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE);
|
public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE);
|
public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE);
|
||||||
@@ -167,11 +178,13 @@ public class Settings extends BaseSettings {
|
|||||||
// Description
|
// Description
|
||||||
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
|
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
|
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_HOW_THIS_WAS_MADE_SECTION = new BooleanSetting("revanced_hide_how_this_was_made_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_INFO_CARDS_SECTION = new BooleanSetting("revanced_hide_info_cards_section", TRUE);
|
public static final BooleanSetting HIDE_INFO_CARDS_SECTION = new BooleanSetting("revanced_hide_info_cards_section", TRUE);
|
||||||
public static final BooleanSetting HIDE_KEY_CONCEPTS_SECTION = new BooleanSetting("revanced_hide_key_concepts_section", FALSE);
|
public static final BooleanSetting HIDE_KEY_CONCEPTS_SECTION = new BooleanSetting("revanced_hide_key_concepts_section", FALSE);
|
||||||
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
|
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
|
||||||
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||||
// Action buttons
|
// Action buttons
|
||||||
|
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
|
||||||
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
|
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
||||||
@@ -196,15 +209,16 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
|
public static final BooleanSetting HIDE_PLAYER_FLYOUT_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
|
||||||
|
|
||||||
// General layout
|
// General layout
|
||||||
|
public static final BooleanSetting RESTORE_OLD_SETTINGS_MENUS = new BooleanSetting("revanced_restore_old_settings_menus", FALSE, true);
|
||||||
|
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
|
||||||
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
|
||||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
|
||||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
||||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
||||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
||||||
public static final BooleanSetting TABLET_LAYOUT = new BooleanSetting("revanced_tablet_layout", FALSE, true, "revanced_tablet_layout_user_dialog_message");
|
|
||||||
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
||||||
public static final EnumSetting<StartPage> CHANGE_START_PAGE = new EnumSetting<>("revanced_change_start_page", StartPage.ORIGINAL, 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.35.36" : "17.33.42", true, parent(SPOOF_APP_VERSION));
|
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));
|
||||||
// Custom filter
|
// Custom filter
|
||||||
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
||||||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||||
@@ -214,17 +228,24 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting HIDE_SHORTS_BUTTON = new BooleanSetting("revanced_hide_shorts_button", TRUE, true);
|
public static final BooleanSetting HIDE_SHORTS_BUTTON = new BooleanSetting("revanced_hide_shorts_button", TRUE, true);
|
||||||
public static final BooleanSetting HIDE_SUBSCRIPTIONS_BUTTON = new BooleanSetting("revanced_hide_subscriptions_button", FALSE, true);
|
public static final BooleanSetting HIDE_SUBSCRIPTIONS_BUTTON = new BooleanSetting("revanced_hide_subscriptions_button", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_NAVIGATION_BUTTON_LABELS = new BooleanSetting("revanced_hide_navigation_button_labels", FALSE, true);
|
public static final BooleanSetting HIDE_NAVIGATION_BUTTON_LABELS = new BooleanSetting("revanced_hide_navigation_button_labels", FALSE, true);
|
||||||
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true);
|
public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true);
|
||||||
|
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true,
|
||||||
|
"revanced_switch_create_with_notifications_button_user_dialog_message");
|
||||||
|
public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", FALSE, true);
|
||||||
|
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true);
|
||||||
|
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK = new BooleanSetting("revanced_disable_translucent_navigation_bar_dark", FALSE, true);
|
||||||
|
|
||||||
// Shorts
|
// Shorts
|
||||||
public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE);
|
public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE);
|
||||||
public static final BooleanSetting DISABLE_SHORTS_BACKGROUND_PLAYBACK = new BooleanSetting("revanced_shorts_disable_background_playback", FALSE);
|
public static final BooleanSetting DISABLE_SHORTS_BACKGROUND_PLAYBACK = new BooleanSetting("revanced_shorts_disable_background_playback", FALSE);
|
||||||
|
public static final EnumSetting<ShortsPlayerType> SHORTS_PLAYER_TYPE = new EnumSetting<>("revanced_shorts_player_type", ShortsPlayerType.SHORTS_PLAYER);
|
||||||
public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_GREEN_SCREEN_BUTTON = new BooleanSetting("revanced_hide_shorts_green_screen_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_GREEN_SCREEN_BUTTON = new BooleanSetting("revanced_hide_shorts_green_screen_button", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE);
|
||||||
|
public static final BooleanSetting HIDE_SHORTS_HISTORY = new BooleanSetting("revanced_hide_shorts_history", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
|
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_INFO_PANEL = new BooleanSetting("revanced_hide_shorts_info_panel", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_INFO_PANEL = new BooleanSetting("revanced_hide_shorts_info_panel", TRUE);
|
||||||
public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
|
public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
|
||||||
@@ -253,18 +274,19 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting SHORTS_AUTOPLAY_BACKGROUND = new BooleanSetting("revanced_shorts_autoplay_background", TRUE);
|
public static final BooleanSetting SHORTS_AUTOPLAY_BACKGROUND = new BooleanSetting("revanced_shorts_autoplay_background", TRUE);
|
||||||
|
|
||||||
// Seekbar
|
// Seekbar
|
||||||
|
public static final BooleanSetting DISABLE_PRECISE_SEEKING_GESTURE = new BooleanSetting("revanced_disable_precise_seeking_gesture", FALSE);
|
||||||
public static final BooleanSetting DISABLE_PRECISE_SEEKING_GESTURE = new BooleanSetting("revanced_disable_precise_seeking_gesture", TRUE);
|
|
||||||
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true);
|
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE);
|
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE, true);
|
||||||
public static final BooleanSetting HIDE_TIMESTAMP = new BooleanSetting("revanced_hide_timestamp", FALSE);
|
public static final BooleanSetting HIDE_TIMESTAMP = new BooleanSetting("revanced_hide_timestamp", FALSE);
|
||||||
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE);
|
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE);
|
||||||
public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", FALSE, true);
|
public static final BooleanSetting SEEKBAR_TAPPING = new BooleanSetting("revanced_seekbar_tapping", FALSE);
|
||||||
public static final BooleanSetting SEEKBAR_TAPPING = new BooleanSetting("revanced_seekbar_tapping", TRUE);
|
|
||||||
public static final BooleanSetting SEEKBAR_THUMBNAILS_HIGH_QUALITY = new BooleanSetting("revanced_seekbar_thumbnails_high_quality", FALSE, true,
|
public static final BooleanSetting SEEKBAR_THUMBNAILS_HIGH_QUALITY = new BooleanSetting("revanced_seekbar_thumbnails_high_quality", FALSE, true,
|
||||||
"revanced_seekbar_thumbnails_high_quality_dialog_message", new SeekbarThumbnailsHighQualityAvailability());
|
"revanced_seekbar_thumbnails_high_quality_dialog_message", new SeekbarThumbnailsHighQualityAvailability());
|
||||||
public static final BooleanSetting SLIDE_TO_SEEK = new BooleanSetting("revanced_slide_to_seek", FALSE, true);
|
public static final BooleanSetting SLIDE_TO_SEEK = new BooleanSetting("revanced_slide_to_seek", FALSE, true);
|
||||||
public static final StringSetting SEEKBAR_CUSTOM_COLOR_VALUE = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033", true, parent(SEEKBAR_CUSTOM_COLOR));
|
public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", FALSE, true);
|
||||||
|
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
|
||||||
|
public static final StringSetting SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_primary", "#FF0033", true, parent(SEEKBAR_CUSTOM_COLOR));
|
||||||
|
public static final StringSetting SEEKBAR_CUSTOM_COLOR_ACCENT = new StringSetting("revanced_seekbar_custom_color_accent", "#FF2791", true, parent(SEEKBAR_CUSTOM_COLOR));
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
||||||
@@ -285,20 +307,22 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
|
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
|
||||||
|
|
||||||
// Swipe controls
|
// Swipe controls
|
||||||
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", TRUE);
|
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true);
|
||||||
public static final BooleanSetting SWIPE_VOLUME = new BooleanSetting("revanced_swipe_volume", TRUE);
|
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", FALSE, true);
|
||||||
|
public static final BooleanSetting SWIPE_VOLUME = new BooleanSetting("revanced_swipe_volume", FALSE, true);
|
||||||
public static final BooleanSetting SWIPE_PRESS_TO_ENGAGE = new BooleanSetting("revanced_swipe_press_to_engage", FALSE, true,
|
public static final BooleanSetting SWIPE_PRESS_TO_ENGAGE = new BooleanSetting("revanced_swipe_press_to_engage", FALSE, true,
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
public static final BooleanSetting SWIPE_HAPTIC_FEEDBACK = new BooleanSetting("revanced_swipe_haptic_feedback", TRUE, true,
|
public static final BooleanSetting SWIPE_HAPTIC_FEEDBACK = new BooleanSetting("revanced_swipe_haptic_feedback", TRUE, true,
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
|
public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
public static final IntegerSetting SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127, true,
|
public static final BooleanSetting SWIPE_SHOW_CIRCULAR_OVERLAY = new BooleanSetting("revanced_swipe_show_circular_overlay", FALSE, true,
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
|
public static final BooleanSetting SWIPE_OVERLAY_MINIMAL_STYLE = new BooleanSetting("revanced_swipe_overlay_minimal_style", FALSE, true,
|
||||||
// Debugging
|
|
||||||
public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 22, true,
|
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
|
public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true,
|
||||||
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
|
private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127);
|
||||||
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
|
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
|
||||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||||
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true, parent(SWIPE_BRIGHTNESS));
|
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true, parent(SWIPE_BRIGHTNESS));
|
||||||
@@ -311,6 +335,7 @@ public class Settings extends BaseSettings {
|
|||||||
public static final BooleanSetting RYD_SHORTS = new BooleanSetting("ryd_shorts", TRUE, parent(RYD_ENABLED));
|
public static final BooleanSetting RYD_SHORTS = new BooleanSetting("ryd_shorts", TRUE, parent(RYD_ENABLED));
|
||||||
public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("ryd_dislike_percentage", FALSE, parent(RYD_ENABLED));
|
public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("ryd_dislike_percentage", FALSE, parent(RYD_ENABLED));
|
||||||
public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("ryd_compact_layout", FALSE, parent(RYD_ENABLED));
|
public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("ryd_compact_layout", FALSE, parent(RYD_ENABLED));
|
||||||
|
public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("ryd_estimated_like", TRUE, parent(RYD_ENABLED));
|
||||||
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED));
|
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED));
|
||||||
|
|
||||||
// SponsorBlock
|
// SponsorBlock
|
||||||
@@ -322,13 +347,14 @@ public class Settings extends BaseSettings {
|
|||||||
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
|
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_CREATE_NEW_SEGMENT = new BooleanSetting("sb_create_new_segment", FALSE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_CREATE_NEW_SEGMENT = new BooleanSetting("sb_create_new_segment", FALSE, parent(SB_ENABLED));
|
||||||
|
public static final BooleanSetting SB_SQUARE_LAYOUT = new BooleanSetting("sb_square_layout", FALSE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_COMPACT_SKIP_BUTTON = new BooleanSetting("sb_compact_skip_button", FALSE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_COMPACT_SKIP_BUTTON = new BooleanSetting("sb_compact_skip_button", FALSE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_AUTO_HIDE_SKIP_BUTTON = new BooleanSetting("sb_auto_hide_skip_button", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_AUTO_HIDE_SKIP_BUTTON = new BooleanSetting("sb_auto_hide_skip_button", TRUE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", TRUE, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_TRACK_SKIP_COUNT = new BooleanSetting("sb_track_skip_count", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_TRACK_SKIP_COUNT = new BooleanSetting("sb_track_skip_count", TRUE, parent(SB_ENABLED));
|
||||||
public static final FloatSetting SB_SEGMENT_MIN_DURATION = new FloatSetting("sb_min_segment_duration", 0F, parent(SB_ENABLED));
|
public static final FloatSetting SB_SEGMENT_MIN_DURATION = new FloatSetting("sb_min_segment_duration", 0F, parent(SB_ENABLED));
|
||||||
public static final BooleanSetting SB_VIDEO_LENGTH_WITHOUT_SEGMENTS = new BooleanSetting("sb_video_length_without_segments", TRUE, parent(SB_ENABLED));
|
public static final BooleanSetting SB_VIDEO_LENGTH_WITHOUT_SEGMENTS = new BooleanSetting("sb_video_length_without_segments", FALSE, parent(SB_ENABLED));
|
||||||
public static final StringSetting SB_API_URL = new StringSetting("sb_api_url", "https://sponsor.ajay.app");
|
public static final StringSetting SB_API_URL = new StringSetting("sb_api_url", "https://sponsor.ajay.app");
|
||||||
public static final BooleanSetting SB_USER_IS_VIP = new BooleanSetting("sb_user_is_vip", FALSE);
|
public static final BooleanSetting SB_USER_IS_VIP = new BooleanSetting("sb_user_is_vip", FALSE);
|
||||||
public static final IntegerSetting SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS = new IntegerSetting("sb_local_time_saved_number_segments", 0);
|
public static final IntegerSetting SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS = new IntegerSetting("sb_local_time_saved_number_segments", 0);
|
||||||
@@ -379,15 +405,39 @@ public class Settings extends BaseSettings {
|
|||||||
|
|
||||||
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
|
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
|
||||||
|
|
||||||
// Old spoof versions that no longer work reliably.
|
// Migrate renamed enum.
|
||||||
if (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) {
|
//noinspection deprecation
|
||||||
Logger.printInfo(() -> "Resetting spoof app version target");
|
if (MINIPLAYER_TYPE.get() == MiniplayerType.PHONE) {
|
||||||
SPOOF_APP_VERSION_TARGET.resetToDefault();
|
MINIPLAYER_TYPE.save(MINIMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate renamed enum.
|
// Migrate old single color seekbar with a slightly brighter accent color based on the primary.
|
||||||
if (MINIPLAYER_TYPE.get() == PHONE) {
|
// Eventually delete this logic.
|
||||||
MINIPLAYER_TYPE.save(MINIMAL);
|
if (!DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY.isSetToDefault()) {
|
||||||
|
try {
|
||||||
|
String oldPrimaryColorString = DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY.get();
|
||||||
|
final int oldPrimaryColor = Color.parseColor(oldPrimaryColorString);
|
||||||
|
SEEKBAR_CUSTOM_COLOR_PRIMARY.save(oldPrimaryColorString);
|
||||||
|
|
||||||
|
final float brightnessScale = 1.3f;
|
||||||
|
final int accentColor = Color.argb(
|
||||||
|
0, // Save without alpha channel.
|
||||||
|
Math.min(255, (int) (brightnessScale * Color.red(oldPrimaryColor))),
|
||||||
|
Math.min(255, (int) (brightnessScale * Color.green(oldPrimaryColor))),
|
||||||
|
Math.min(255, (int) (brightnessScale * Color.blue(oldPrimaryColor)))
|
||||||
|
);
|
||||||
|
|
||||||
|
SEEKBAR_CUSTOM_COLOR_ACCENT.save(String.format("#%06X", accentColor));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Could not parse old seekbar color", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY.resetToDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.isSetToDefault()) {
|
||||||
|
SWIPE_OVERLAY_OPACITY.save(DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.get() / 255);
|
||||||
|
DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.resetToDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package app.revanced.extension.youtube.settings.preference;
|
|
||||||
|
|
||||||
import static app.revanced.extension.shared.StringRef.str;
|
|
||||||
import static app.revanced.extension.shared.spoof.DeviceHardwareSupport.DEVICE_HAS_HARDWARE_DECODING_VP9;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.preference.SwitchPreference;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
|
||||||
public class ForceAVCSpoofingPreference extends SwitchPreference {
|
|
||||||
{
|
|
||||||
if (!DEVICE_HAS_HARDWARE_DECODING_VP9) {
|
|
||||||
setSummaryOn(str("revanced_spoof_video_streams_ios_force_avc_no_hardware_vp9_summary_on"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForceAVCSpoofingPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
|
||||||
super(context, attrs, defStyleAttr, defStyleRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForceAVCSpoofingPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
|
||||||
super(context, attrs, defStyleAttr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForceAVCSpoofingPreference(Context context, AttributeSet attrs) {
|
|
||||||
super(context, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ForceAVCSpoofingPreference(Context context) {
|
|
||||||
super(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateUI() {
|
|
||||||
if (DEVICE_HAS_HARDWARE_DECODING_VP9) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Temporarily remove the preference key to allow changing this preference without
|
|
||||||
// causing the settings UI listeners from showing reboot dialogs by the changes made here.
|
|
||||||
String key = getKey();
|
|
||||||
setKey(null);
|
|
||||||
|
|
||||||
// This setting cannot be changed by the user.
|
|
||||||
super.setEnabled(false);
|
|
||||||
super.setChecked(true);
|
|
||||||
|
|
||||||
setKey(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setEnabled(boolean enabled) {
|
|
||||||
super.setEnabled(enabled);
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setChecked(boolean checked) {
|
|
||||||
super.setChecked(checked);
|
|
||||||
|
|
||||||
updateUI();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package app.revanced.extension.youtube.settings.preference;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.preference.SwitchPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import app.revanced.extension.youtube.patches.ForceOriginalAudioPatch;
|
||||||
|
|
||||||
|
@SuppressWarnings({"deprecation", "unused"})
|
||||||
|
public class ForceOriginalAudioSwitchPreference extends SwitchPreference {
|
||||||
|
|
||||||
|
{
|
||||||
|
if (!ForceOriginalAudioPatch.PATCH_AVAILABLE) {
|
||||||
|
// Show why force audio is not available.
|
||||||
|
String summary = str("revanced_force_original_audio_not_available");
|
||||||
|
setSummary(summary);
|
||||||
|
setSummaryOn(summary);
|
||||||
|
setSummaryOff(summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
public ForceOriginalAudioSwitchPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
public ForceOriginalAudioSwitchPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,18 +3,14 @@ package app.revanced.extension.youtube.settings.preference;
|
|||||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows using basic html for the summary text.
|
* Allows using basic html for the summary text.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings({"unused", "deprecation"})
|
@SuppressWarnings({"unused", "deprecation"})
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
|
||||||
public class HtmlPreference extends Preference {
|
public class HtmlPreference extends Preference {
|
||||||
{
|
{
|
||||||
setSummary(Html.fromHtml(getSummary().toString(), FROM_HTML_MODE_COMPACT));
|
setSummary(Html.fromHtml(getSummary().toString(), FROM_HTML_MODE_COMPACT));
|
||||||
|
|||||||
@@ -10,19 +10,25 @@ import android.os.Build;
|
|||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
|
import android.util.Pair;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toolbar;
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.extension.shared.Logger;
|
import app.revanced.extension.shared.Logger;
|
||||||
import app.revanced.extension.shared.Utils;
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.extension.shared.settings.EnumSetting;
|
||||||
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
|
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
|
||||||
import app.revanced.extension.youtube.ThemeHelper;
|
import app.revanced.extension.youtube.ThemeHelper;
|
||||||
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
|
||||||
|
import app.revanced.extension.youtube.settings.LicenseActivityHook;
|
||||||
import app.revanced.extension.youtube.settings.Settings;
|
import app.revanced.extension.youtube.settings.Settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +47,55 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
return Utils.getContext().getResources().getDrawable(backButtonResource);
|
return Utils.getContext().getResources().getDrawable(backButtonResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
/**
|
||||||
|
* Sorts a preference list by menu entries, but preserves the first value as the first entry.
|
||||||
|
*
|
||||||
|
* @noinspection SameParameterValue
|
||||||
|
*/
|
||||||
|
private static void sortListPreferenceByValues(ListPreference listPreference, int firstEntriesToPreserve) {
|
||||||
|
CharSequence[] entries = listPreference.getEntries();
|
||||||
|
CharSequence[] entryValues = listPreference.getEntryValues();
|
||||||
|
final int entrySize = entries.length;
|
||||||
|
|
||||||
|
if (entrySize != entryValues.length) {
|
||||||
|
// Xml array declaration has a missing/extra entry.
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Pair<String, String>> firstPairs = new ArrayList<>(firstEntriesToPreserve);
|
||||||
|
List<Pair<String, String>> pairsToSort = new ArrayList<>(entrySize);
|
||||||
|
|
||||||
|
for (int i = 0; i < entrySize; i++) {
|
||||||
|
Pair<String, String> pair = new Pair<>(entries[i].toString(), entryValues[i].toString());
|
||||||
|
if (i < firstEntriesToPreserve) {
|
||||||
|
firstPairs.add(pair);
|
||||||
|
} else {
|
||||||
|
pairsToSort.add(pair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Collections.sort(pairsToSort, (pair1, pair2) -> pair1.first.compareToIgnoreCase(pair2.first));
|
||||||
|
|
||||||
|
CharSequence[] sortedEntries = new CharSequence[entrySize];
|
||||||
|
CharSequence[] sortedEntryValues = new CharSequence[entrySize];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (Pair<String, String> pair : firstPairs) {
|
||||||
|
sortedEntries[i] = pair.first;
|
||||||
|
sortedEntryValues[i] = pair.second;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Pair<String, String> pair : pairsToSort) {
|
||||||
|
sortedEntries[i] = pair.first;
|
||||||
|
sortedEntryValues[i] = pair.second;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
listPreference.setEntries(sortedEntries);
|
||||||
|
listPreference.setEntryValues(sortedEntryValues);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initialize() {
|
protected void initialize() {
|
||||||
super.initialize();
|
super.initialize();
|
||||||
@@ -50,15 +104,25 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
setPreferenceScreenToolbar(getPreferenceScreen());
|
setPreferenceScreenToolbar(getPreferenceScreen());
|
||||||
|
|
||||||
// If the preference was included, then initialize it based on the available playback speed.
|
// If the preference was included, then initialize it based on the available playback speed.
|
||||||
Preference defaultSpeedPreference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
|
Preference preference = findPreference(Settings.PLAYBACK_SPEED_DEFAULT.key);
|
||||||
if (defaultSpeedPreference instanceof ListPreference) {
|
if (preference instanceof ListPreference playbackPreference) {
|
||||||
CustomPlaybackSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference);
|
CustomPlaybackSpeedPatch.initializeListPreference(playbackPreference);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sortPreferenceListMenu(Settings.SPOOF_VIDEO_STREAMS_LANGUAGE);
|
||||||
|
sortPreferenceListMenu(BaseSettings.REVANCED_LANGUAGE);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
Logger.printException(() -> "initialize failure", ex);
|
Logger.printException(() -> "initialize failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sortPreferenceListMenu(EnumSetting<?> setting) {
|
||||||
|
Preference preference = findPreference(setting.key);
|
||||||
|
if (preference instanceof ListPreference languagePreference) {
|
||||||
|
sortListPreferenceByValues(languagePreference, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
|
private void setPreferenceScreenToolbar(PreferenceScreen parentScreen) {
|
||||||
for (int i = 0, preferenceCount = parentScreen.getPreferenceCount(); i < preferenceCount; i++) {
|
for (int i = 0, preferenceCount = parentScreen.getPreferenceCount(); i < preferenceCount; i++) {
|
||||||
Preference childPreference = parentScreen.getPreference(i);
|
Preference childPreference = parentScreen.getPreference(i);
|
||||||
@@ -74,9 +138,6 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
.getParent();
|
.getParent();
|
||||||
|
|
||||||
// Fix required for Android 15 and YT 19.45+
|
// Fix required for Android 15 and YT 19.45+
|
||||||
// FIXME:
|
|
||||||
// On Android 15 the text layout is not aligned the same as the parent
|
|
||||||
// screen and it looks a little off. Otherwise this works.
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
|
rootView.setOnApplyWindowInsetsListener((v, insets) -> {
|
||||||
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
|
Insets statusInsets = insets.getInsets(WindowInsets.Type.statusBars());
|
||||||
@@ -89,13 +150,10 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
toolbar.setTitle(childScreen.getTitle());
|
toolbar.setTitle(childScreen.getTitle());
|
||||||
toolbar.setNavigationIcon(getBackButtonDrawable());
|
toolbar.setNavigationIcon(getBackButtonDrawable());
|
||||||
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
|
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
|
||||||
|
final int margin = (int) TypedValue.applyDimension(
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
|
||||||
final int margin = (int) TypedValue.applyDimension(
|
);
|
||||||
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
|
toolbar.setTitleMargin(margin, 0, margin, 0);
|
||||||
);
|
|
||||||
toolbar.setTitleMargin(margin, 0, margin, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
TextView toolbarTextView = Utils.getChildView(toolbar,
|
TextView toolbarTextView = Utils.getChildView(toolbar,
|
||||||
true, TextView.class::isInstance);
|
true, TextView.class::isInstance);
|
||||||
@@ -103,6 +161,8 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
|||||||
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor());
|
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LicenseActivityHook.setToolbarLayoutParams(toolbar);
|
||||||
|
|
||||||
rootView.addView(toolbar, 0);
|
rootView.addView(toolbar, 0);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
|
|||||||
*/
|
*/
|
||||||
private SwitchPreference compactLayoutPreference;
|
private SwitchPreference compactLayoutPreference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If hidden likes are replaced with an estimated value.
|
||||||
|
*/
|
||||||
|
private SwitchPreference estimatedLikesPreference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If segmented like/dislike button uses smaller compact layout.
|
* If segmented like/dislike button uses smaller compact layout.
|
||||||
*/
|
*/
|
||||||
@@ -48,6 +53,7 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
|
|||||||
shortsPreference.setEnabled(Settings.RYD_SHORTS.isAvailable());
|
shortsPreference.setEnabled(Settings.RYD_SHORTS.isAvailable());
|
||||||
percentagePreference.setEnabled(Settings.RYD_DISLIKE_PERCENTAGE.isAvailable());
|
percentagePreference.setEnabled(Settings.RYD_DISLIKE_PERCENTAGE.isAvailable());
|
||||||
compactLayoutPreference.setEnabled(Settings.RYD_COMPACT_LAYOUT.isAvailable());
|
compactLayoutPreference.setEnabled(Settings.RYD_COMPACT_LAYOUT.isAvailable());
|
||||||
|
estimatedLikesPreference.setEnabled(Settings.RYD_ESTIMATED_LIKE.isAvailable());
|
||||||
toastOnRYDNotAvailable.setEnabled(Settings.RYD_TOAST_ON_CONNECTION_ERROR.isAvailable());
|
toastOnRYDNotAvailable.setEnabled(Settings.RYD_TOAST_ON_CONNECTION_ERROR.isAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +123,19 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
|
|||||||
});
|
});
|
||||||
preferenceScreen.addPreference(compactLayoutPreference);
|
preferenceScreen.addPreference(compactLayoutPreference);
|
||||||
|
|
||||||
|
estimatedLikesPreference = new SwitchPreference(context);
|
||||||
|
estimatedLikesPreference.setChecked(Settings.RYD_ESTIMATED_LIKE.get());
|
||||||
|
estimatedLikesPreference.setTitle(str("revanced_ryd_estimated_like_title"));
|
||||||
|
estimatedLikesPreference.setSummaryOn(str("revanced_ryd_estimated_like_summary_on"));
|
||||||
|
estimatedLikesPreference.setSummaryOff(str("revanced_ryd_estimated_like_summary_off"));
|
||||||
|
estimatedLikesPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||||
|
Settings.RYD_ESTIMATED_LIKE.save((Boolean) newValue);
|
||||||
|
ReturnYouTubeDislike.clearAllUICaches();
|
||||||
|
updateUIState();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
preferenceScreen.addPreference(estimatedLikesPreference);
|
||||||
|
|
||||||
toastOnRYDNotAvailable = new SwitchPreference(context);
|
toastOnRYDNotAvailable = new SwitchPreference(context);
|
||||||
toastOnRYDNotAvailable.setChecked(Settings.RYD_TOAST_ON_CONNECTION_ERROR.get());
|
toastOnRYDNotAvailable.setChecked(Settings.RYD_TOAST_ON_CONNECTION_ERROR.get());
|
||||||
toastOnRYDNotAvailable.setTitle(str("revanced_ryd_toast_on_connection_error_title"));
|
toastOnRYDNotAvailable.setTitle(str("revanced_ryd_toast_on_connection_error_title"));
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import android.content.Context;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.*;
|
import android.preference.*;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
@@ -37,8 +36,9 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||||||
private SwitchPreference sbEnabled;
|
private SwitchPreference sbEnabled;
|
||||||
private SwitchPreference addNewSegment;
|
private SwitchPreference addNewSegment;
|
||||||
private SwitchPreference votingEnabled;
|
private SwitchPreference votingEnabled;
|
||||||
private SwitchPreference compactSkipButton;
|
|
||||||
private SwitchPreference autoHideSkipSegmentButton;
|
private SwitchPreference autoHideSkipSegmentButton;
|
||||||
|
private SwitchPreference compactSkipButton;
|
||||||
|
private SwitchPreference squareLayout;
|
||||||
private SwitchPreference showSkipToast;
|
private SwitchPreference showSkipToast;
|
||||||
private SwitchPreference trackSkips;
|
private SwitchPreference trackSkips;
|
||||||
private SwitchPreference showTimeWithoutSegments;
|
private SwitchPreference showTimeWithoutSegments;
|
||||||
@@ -62,7 +62,9 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||||||
} else if (!Settings.SB_CREATE_NEW_SEGMENT.get()) {
|
} else if (!Settings.SB_CREATE_NEW_SEGMENT.get()) {
|
||||||
SponsorBlockViewController.hideNewSegmentLayout();
|
SponsorBlockViewController.hideNewSegmentLayout();
|
||||||
}
|
}
|
||||||
// Voting and add new segment buttons automatically shows/hide themselves.
|
// Voting and add new segment buttons automatically show/hide themselves.
|
||||||
|
|
||||||
|
SponsorBlockViewController.updateLayout();
|
||||||
|
|
||||||
sbEnabled.setChecked(enabled);
|
sbEnabled.setChecked(enabled);
|
||||||
|
|
||||||
@@ -72,11 +74,14 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||||||
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
|
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
|
||||||
votingEnabled.setEnabled(enabled);
|
votingEnabled.setEnabled(enabled);
|
||||||
|
|
||||||
|
autoHideSkipSegmentButton.setEnabled(enabled);
|
||||||
|
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
|
||||||
|
|
||||||
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
|
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
|
||||||
compactSkipButton.setEnabled(enabled);
|
compactSkipButton.setEnabled(enabled);
|
||||||
|
|
||||||
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
|
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
|
||||||
autoHideSkipSegmentButton.setEnabled(enabled);
|
squareLayout.setEnabled(enabled);
|
||||||
|
|
||||||
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
|
||||||
showSkipToast.setEnabled(enabled);
|
showSkipToast.setEnabled(enabled);
|
||||||
@@ -176,6 +181,17 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
autoHideSkipSegmentButton = new SwitchPreference(context);
|
||||||
|
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
|
||||||
|
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
|
||||||
|
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
|
||||||
|
category.addPreference(autoHideSkipSegmentButton);
|
||||||
|
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
|
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
|
||||||
|
updateUI();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
compactSkipButton = new SwitchPreference(context);
|
compactSkipButton = new SwitchPreference(context);
|
||||||
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
|
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
|
||||||
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
|
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
|
||||||
@@ -187,13 +203,13 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
autoHideSkipSegmentButton = new SwitchPreference(context);
|
squareLayout = new SwitchPreference(context);
|
||||||
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
|
squareLayout.setTitle(str("revanced_sb_square_layout"));
|
||||||
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
|
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
|
||||||
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
|
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
|
||||||
category.addPreference(autoHideSkipSegmentButton);
|
category.addPreference(squareLayout);
|
||||||
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
|
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -393,9 +409,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||||||
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
|
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
|
||||||
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
importExport.getEditText().setAutofillHints((String) null);
|
||||||
importExport.getEditText().setAutofillHints((String) null);
|
|
||||||
}
|
|
||||||
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
|
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
|
||||||
importExport.setOnPreferenceClickListener(preference1 -> {
|
importExport.setOnPreferenceClickListener(preference1 -> {
|
||||||
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
|
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
|
||||||
@@ -507,7 +521,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||||||
Utils.showToastLong(str("revanced_sb_stats_username_changed"));
|
Utils.showToastLong(str("revanced_sb_stats_username_changed"));
|
||||||
} else {
|
} else {
|
||||||
preference.setText(userName); // revert to previous
|
preference.setText(userName); // revert to previous
|
||||||
Utils.showToastLong(errorMessage);
|
SponsorBlockUtils.showErrorDialog(errorMessage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package app.revanced.extension.youtube.settings.preference;
|
||||||
|
|
||||||
|
import static app.revanced.extension.shared.StringRef.str;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.extension.shared.Logger;
|
||||||
|
import app.revanced.extension.shared.Utils;
|
||||||
|
import app.revanced.extension.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.extension.shared.settings.Setting;
|
||||||
|
import app.revanced.extension.shared.spoof.ClientType;
|
||||||
|
|
||||||
|
@SuppressWarnings({"deprecation", "unused"})
|
||||||
|
public class SpoofStreamingDataSideEffectsPreference extends Preference {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ClientType currentClientType;
|
||||||
|
|
||||||
|
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||||
|
// Because this listener may run before the ReVanced settings fragment updates Settings,
|
||||||
|
// this could show the prior config and not the current.
|
||||||
|
//
|
||||||
|
// Push this call to the end of the main run queue,
|
||||||
|
// so all other listeners are done and Settings is up to date.
|
||||||
|
Utils.runOnMainThread(this::updateUI);
|
||||||
|
};
|
||||||
|
|
||||||
|
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SpoofStreamingDataSideEffectsPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addChangeListener() {
|
||||||
|
Setting.preferences.preferences.registerOnSharedPreferenceChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeChangeListener() {
|
||||||
|
Setting.preferences.preferences.unregisterOnSharedPreferenceChangeListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
|
||||||
|
super.onAttachedToHierarchy(preferenceManager);
|
||||||
|
updateUI();
|
||||||
|
addChangeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPrepareForRemoval() {
|
||||||
|
super.onPrepareForRemoval();
|
||||||
|
removeChangeListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUI() {
|
||||||
|
ClientType clientType = BaseSettings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get();
|
||||||
|
if (currentClientType == clientType) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Updating spoof stream side effects preference");
|
||||||
|
setEnabled(BaseSettings.SPOOF_VIDEO_STREAMS.get());
|
||||||
|
|
||||||
|
String key = "revanced_spoof_video_streams_about_" +
|
||||||
|
(clientType == ClientType.IOS_UNPLUGGED
|
||||||
|
? "ios_tv"
|
||||||
|
: "android");
|
||||||
|
String title = str(key + "_title");
|
||||||
|
String summary = str(key + "_summary");
|
||||||
|
|
||||||
|
// Android VR supports AV1 but all other clients do not.
|
||||||
|
if (clientType != ClientType.ANDROID_VR_AUTH && clientType != ClientType.ANDROID_VR_NO_AUTH) {
|
||||||
|
summary += '\n' + str("revanced_spoof_video_streams_about_no_av1");
|
||||||
|
}
|
||||||
|
|
||||||
|
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
|
||||||
|
|
||||||
|
setTitle(title);
|
||||||
|
setSummary(summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
@@ -54,20 +55,21 @@ public final class NavigationBar {
|
|||||||
* How long to wait for the set nav button latch to be released. Maximum wait time must
|
* How long to wait for the set nav button latch to be released. Maximum wait time must
|
||||||
* be as small as possible while still allowing enough time for the nav bar to update.
|
* be as small as possible while still allowing enough time for the nav bar to update.
|
||||||
*
|
*
|
||||||
* YT calls it's back button handlers out of order,
|
* YT calls it's back button handlers out of order, and litho starts filtering before the
|
||||||
* and litho starts filtering before the navigation bar is updated.
|
* navigation bar is updated. Fixing this situation and not needlessly waiting requires
|
||||||
|
* somehow detecting if a back button key/gesture will not change the active tab.
|
||||||
*
|
*
|
||||||
* Fixing this situation and not needlessly waiting requires somehow
|
* On average the time between pressing the back button and the first litho event is
|
||||||
* detecting if a back button key-press will cause a tab change.
|
* about 10-20ms. Waiting up to 75-150ms should be enough time to handle normal use cases
|
||||||
|
* and not be noticeable, since YT typically takes 100-200ms (or more) to update the view.
|
||||||
*
|
*
|
||||||
* Typically after pressing the back button, the time between the first litho event and
|
* This delay is only noticeable when the device back button/gesture will not
|
||||||
* when the nav button is updated is about 10-20ms. Using 50-100ms here should be enough time
|
* change the current navigation tab, such as backing out of the watch history.
|
||||||
* and not noticeable, since YT typically takes 100-200ms (or more) to update the view anyways.
|
|
||||||
*
|
*
|
||||||
* This issue can also be avoided on a patch by patch basis, by avoiding calls to
|
* This issue can also be avoided on a patch by patch basis, by avoiding calls to
|
||||||
* {@link NavigationButton#getSelectedNavigationButton()} unless absolutely necessary.
|
* {@link NavigationButton#getSelectedNavigationButton()} unless absolutely necessary.
|
||||||
*/
|
*/
|
||||||
private static final long LATCH_AWAIT_TIMEOUT_MILLISECONDS = 75;
|
private static final long LATCH_AWAIT_TIMEOUT_MILLISECONDS = 120;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used as a workaround to fix the issue of YT calling back button handlers out of order.
|
* Used as a workaround to fix the issue of YT calling back button handlers out of order.
|
||||||
@@ -113,7 +115,8 @@ public final class NavigationBar {
|
|||||||
// The latch is released from the main thread, and waiting from the main thread will always timeout.
|
// The latch is released from the main thread, and waiting from the main thread will always timeout.
|
||||||
// This situation has only been observed when navigating out of a submenu and not changing tabs.
|
// This situation has only been observed when navigating out of a submenu and not changing tabs.
|
||||||
// and for that use case the nav bar does not change so it's safe to return here.
|
// and for that use case the nav bar does not change so it's safe to return here.
|
||||||
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. Using last known navbar button status.");
|
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. " +
|
||||||
|
"Using last known navbar button status.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +134,9 @@ public final class NavigationBar {
|
|||||||
Logger.printDebug(() -> "Latch wait timed out");
|
Logger.printDebug(() -> "Latch wait timed out");
|
||||||
|
|
||||||
} catch (InterruptedException ex) {
|
} catch (InterruptedException ex) {
|
||||||
Logger.printException(() -> "Latch wait interrupted failure", ex); // Will never happen.
|
// Calling YouTube thread was interrupted.
|
||||||
|
Logger.printException(() -> "Latch wait interrupted", ex);
|
||||||
|
Thread.currentThread().interrupt(); // Restore interrupt status flag.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +243,29 @@ public final class NavigationBar {
|
|||||||
// Code is added during patching.
|
// Code is added during patching.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the bundled non cairo filled icon instead of a custom icon.
|
||||||
|
* Use the old non cairo filled icon, which is almost identical to
|
||||||
|
* the what would be the filled cairo icon.
|
||||||
|
*/
|
||||||
|
private static final int fillBellCairoBlack = Utils.getResourceIdentifier(
|
||||||
|
"yt_fill_bell_black_24", "drawable");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
* Fixes missing drawable.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
public static void setCairoNotificationFilledIcon(EnumMap enumMap, Enum tabActivityCairo) {
|
||||||
|
if (fillBellCairoBlack != 0) {
|
||||||
|
// Show a popup informing this fix is no longer needed to those who might care.
|
||||||
|
if (BaseSettings.DEBUG.get() && enumMap.containsKey(tabActivityCairo)) {
|
||||||
|
Logger.printException(() -> "YouTube fixed the cairo notification icons");
|
||||||
|
}
|
||||||
|
enumMap.putIfAbsent(tabActivityCairo, fillBellCairoBlack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum NavigationButton {
|
public enum NavigationButton {
|
||||||
HOME("PIVOT_HOME", "TAB_HOME_CAIRO"),
|
HOME("PIVOT_HOME", "TAB_HOME_CAIRO"),
|
||||||
SHORTS("TAB_SHORTS", "TAB_SHORTS_CAIRO"),
|
SHORTS("TAB_SHORTS", "TAB_SHORTS_CAIRO"),
|
||||||
@@ -246,6 +274,10 @@ public final class NavigationBar {
|
|||||||
* This tab will never be in a selected state, even if the create video UI is on screen.
|
* This tab will never be in a selected state, even if the create video UI is on screen.
|
||||||
*/
|
*/
|
||||||
CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"),
|
CREATE("CREATION_TAB_LARGE", "CREATION_TAB_LARGE_CAIRO"),
|
||||||
|
/**
|
||||||
|
* Only shown to automotive layout.
|
||||||
|
*/
|
||||||
|
EXPLORE("TAB_EXPLORE"),
|
||||||
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"),
|
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", "TAB_SUBSCRIPTIONS_CAIRO"),
|
||||||
/**
|
/**
|
||||||
* Notifications tab. Only present when
|
* Notifications tab. Only present when
|
||||||
@@ -283,8 +315,8 @@ public final class NavigationBar {
|
|||||||
*
|
*
|
||||||
* All code calling this method should handle a null return value.
|
* All code calling this method should handle a null return value.
|
||||||
*
|
*
|
||||||
* <b>Due to issues with how YT processes physical back button events,
|
* <b>Due to issues with how YT processes physical back button/gesture events,
|
||||||
* this patch uses workarounds that can cause this method to take up to 75ms
|
* this patch uses workarounds that can cause this method to take up to 120ms
|
||||||
* if the device back button was recently pressed.</b>
|
* if the device back button was recently pressed.</b>
|
||||||
*
|
*
|
||||||
* @return The active navigation tab.
|
* @return The active navigation tab.
|
||||||
|
|||||||
@@ -21,9 +21,6 @@ enum class PlayerType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A regular video is minimized.
|
* A regular video is minimized.
|
||||||
*
|
|
||||||
* When spoofing to 16.x YouTube and watching a short with a regular video in the background,
|
|
||||||
* the type can be this (and not [HIDDEN]).
|
|
||||||
*/
|
*/
|
||||||
WATCH_WHILE_MINIMIZED,
|
WATCH_WHILE_MINIMIZED,
|
||||||
WATCH_WHILE_MAXIMIZED,
|
WATCH_WHILE_MAXIMIZED,
|
||||||
@@ -56,8 +53,7 @@ enum class PlayerType {
|
|||||||
val newType = nameToPlayerType[enumName]
|
val newType = nameToPlayerType[enumName]
|
||||||
if (newType == null) {
|
if (newType == null) {
|
||||||
Logger.printException { "Unknown PlayerType encountered: $enumName" }
|
Logger.printException { "Unknown PlayerType encountered: $enumName" }
|
||||||
} else if (current != newType) {
|
} else {
|
||||||
Logger.printDebug { "PlayerType changed to: $newType" }
|
|
||||||
current = newType
|
current = newType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,12 +64,16 @@ enum class PlayerType {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
var current
|
var current
|
||||||
get() = currentPlayerType
|
get() = currentPlayerType
|
||||||
private set(value) {
|
private set(type) {
|
||||||
currentPlayerType = value
|
if (currentPlayerType != type) {
|
||||||
onChange(currentPlayerType)
|
Logger.printDebug { "Changed to: $type" }
|
||||||
|
|
||||||
|
currentPlayerType = type
|
||||||
|
onChange(type)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Volatile // value is read/write from different threads
|
@Volatile // Read/write from different threads.
|
||||||
private var currentPlayerType = NONE
|
private var currentPlayerType = NONE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user