Compare commits

...

53 Commits

Author SHA1 Message Date
semantic-release-bot
dcfbd8bf93 chore: Release v5.28.1-dev.2 [skip ci]
## [5.28.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.1...v5.28.1-dev.2) (2025-06-23)

### Bug Fixes

* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([b656976](b65697603d))
2025-06-23 07:19:38 +00:00
ILoveOpenSourceApplications
b65697603d fix(YouTube - Hide ads): Hide new type of product ad in video description (#5225) 2025-06-23 11:17:08 +04:00
semantic-release-bot
25da5cca8b chore: Release v5.28.1-dev.1 [skip ci]
## [5.28.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.28.1-dev.1) (2025-06-22)

### Bug Fixes

* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([2b62fc2](2b62fc2224))
2025-06-22 11:23:52 +00:00
MarcaD
2b62fc2224 fix: Add scrollable content to modern style settings dialogs (#5211) 2025-06-22 15:21:00 +04:00
semantic-release-bot
a9e9456b6b chore: Release v5.28.0 [skip ci]
# [5.28.0](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0) (2025-06-20)

### Bug Fixes

* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([1cea6bf](1cea6bfdff))
* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([a8d2a1e](a8d2a1e028))
* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([90868ff](90868ff025))
* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([5540136](55401368b8))
* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([e138501](e138501657))
* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([e790cfb](e790cfbf59))

### Features

* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([7bbaca7](7bbaca77ad))
* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([a426e2a](a426e2af50))
2025-06-20 08:17:57 +00:00
LisoUseInAIKyrios
b01523e97d chore: Merge branch dev to main (#5160) 2025-06-20 12:14:29 +04:00
github-actions[bot]
b8afb4e821 chore: Sync translations (#5210) 2025-06-20 12:13:39 +04:00
github-actions[bot]
0d2198faed chore: Sync translations (#5207) 2025-06-20 01:33:20 +04:00
semantic-release-bot
5c7c407b82 chore: Release v5.28.0-dev.8 [skip ci]
# [5.28.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.7...v5.28.0-dev.8) (2025-06-19)

### Bug Fixes

* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([a8d2a1e](a8d2a1e028))
2025-06-19 06:10:06 +00:00
Dawid Krajcarz
a8d2a1e028 fix(Messenger - Remove Meta AI): Improve patch logic (#5153) 2025-06-19 10:06:46 +04:00
semantic-release-bot
d31624cae8 chore: Release v5.28.0-dev.7 [skip ci]
# [5.28.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.6...v5.28.0-dev.7) (2025-06-18)

### Bug Fixes

* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([e790cfb](e790cfbf59))
2025-06-18 10:38:43 +00:00
LisoUseInAIKyrios
e790cfbf59 fix(YouTube): Remove old app targets that are no longer supported by YouTube (#5192) 2025-06-18 12:35:43 +02:00
LisoUseInAIKyrios
a54d408d3e chore: Fix string typos, fix missing long/wide returnEarly/returnLate 2025-06-18 11:06:48 +02:00
semantic-release-bot
5d3769e921 chore: Release v5.28.0-dev.6 [skip ci]
# [5.28.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.5...v5.28.0-dev.6) (2025-06-17)

### Bug Fixes

* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([e138501](e138501657))
2025-06-17 21:07:00 +00:00
LisoUseInAIKyrios
e138501657 fix(Threads - Hide ads): Constrain patch to the last working app target (#5189) 2025-06-17 23:03:35 +02:00
semantic-release-bot
a9235d6b62 chore: Release v5.28.0-dev.5 [skip ci]
# [5.28.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.4...v5.28.0-dev.5) (2025-06-17)

### Bug Fixes

* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([90868ff](90868ff025))
2025-06-17 06:47:11 +00:00
hoodles
90868ff025 fix(Pandora - Disable ads): Support latest app target (#5185) 2025-06-17 08:44:19 +02:00
github-actions[bot]
345ec5c430 chore: Sync translations (#5188) 2025-06-17 08:43:55 +02:00
semantic-release-bot
c8b95d475c chore: Release v5.28.0-dev.4 [skip ci]
# [5.28.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.3...v5.28.0-dev.4) (2025-06-13)

### Features

* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([a426e2a](a426e2af50))
2025-06-13 07:33:27 +00:00
github-actions[bot]
0a93f44a5e chore: Sync translations (#5167) 2025-06-13 09:30:30 +02:00
MarcaD
a426e2af50 feat: Use modern style settings dialogs (#5109)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-06-13 09:29:13 +02:00
semantic-release-bot
9e30c34e74 chore: Release v5.28.0-dev.3 [skip ci]
# [5.28.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.2...v5.28.0-dev.3) (2025-06-11)

### Bug Fixes

* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([5540136](55401368b8))
2025-06-11 20:23:04 +00:00
Nuckyz
55401368b8 fix(Spotify): Fix Hide Create button and Sanitize sharing links for older but supported app targets (#5159) 2025-06-11 17:20:19 -03:00
LisoUseInAIKyrios
c0c56fef23 chore: Fix debug logging if context is not set 2025-06-11 19:57:37 +02:00
semantic-release-bot
69df47602f chore: Release v5.28.0-dev.2 [skip ci]
# [5.28.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.1...v5.28.0-dev.2) (2025-06-11)

### Bug Fixes

* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([1cea6bf](1cea6bfdff))
2025-06-11 17:44:07 +00:00
LisoUseInAIKyrios
1cea6bfdff fix(Google Photos): Resolve startup crash if MicroG GmsCore does not already have granted permissions 2025-06-11 19:40:37 +02:00
semantic-release-bot
e2a9552f91 chore: Release v5.28.0-dev.1 [skip ci]
# [5.28.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0-dev.1) (2025-06-11)

### Features

* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([7bbaca7](7bbaca77ad))
2025-06-11 08:28:44 +00:00
brosssh
7bbaca77ad feat(Spotify): Add Change lyrics provider patch (#4937) 2025-06-11 10:25:58 +02:00
semantic-release-bot
246f3efe55 chore: Release v5.27.0 [skip ci]
# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09)

### Bug Fixes

* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([c0448de](c0448dece4))
* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([7a432e5](7a432e5741))
* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([65fc6b4](65fc6b43f5))
* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([4b8499f](4b8499ff2c))
* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([9d10ab6](9d10ab6c00))
* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([6aff8e8](6aff8e8ca4))

### Features

* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([6127f48](6127f48a9e))
* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([af827e2](af827e2f1a))
* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([ed0d807](ed0d807d70))
* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([adfac8a](adfac8a1f2))
* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([f8e31c8](f8e31c820a))
* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([6d5380d](6d5380d44d))
2025-06-09 18:37:59 +00:00
LisoUseInAIKyrios
0fca3e8fb1 chore: Merge branch dev to main (#5115) 2025-06-09 20:35:14 +02:00
semantic-release-bot
c09255eaed chore: Release v5.27.0-dev.9 [skip ci]
# [5.27.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.8...v5.27.0-dev.9) (2025-06-09)

### Features

* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([ed0d807](ed0d807d70))
2025-06-09 18:18:19 +00:00
github-actions[bot]
e78d6240ea chore: Sync translations (#5151) 2025-06-09 20:15:28 +02:00
Dawid Krajcarz
ed0d807d70 feat(Messenger): Add Hide Facebook button patch (#5057) 2025-06-09 20:13:05 +02:00
semantic-release-bot
1c39004350 chore: Release v5.27.0-dev.8 [skip ci]
# [5.27.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.7...v5.27.0-dev.8) (2025-06-09)

### Features

* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([6127f48](6127f48a9e))
2025-06-09 14:11:00 +00:00
ILoveOpenSourceApplications
6127f48a9e feat: Add Hide app icon patch (#4977) 2025-06-09 11:07:43 -03:00
semantic-release-bot
ad416f4aa7 chore: Release v5.27.0-dev.7 [skip ci]
# [5.27.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.6...v5.27.0-dev.7) (2025-06-08)

### Features

* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([adfac8a](adfac8a1f2))
2025-06-08 18:07:53 +00:00
Nuckyz
adfac8a1f2 feat(YouTube - Hide player overlay buttons): Add in app setting for "Hide player control buttons background" (#5147) 2025-06-08 15:04:58 -03:00
semantic-release-bot
498488d45b chore: Release v5.27.0-dev.6 [skip ci]
# [5.27.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.5...v5.27.0-dev.6) (2025-06-08)

### Features

* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([f8e31c8](f8e31c820a))
2025-06-08 12:06:28 +00:00
LisoUseInAIKyrios
f8e31c820a feat(YouTube - Hide Shorts components): Add hide 'New posts' button 2025-06-08 14:03:00 +02:00
semantic-release-bot
826a391591 chore: Release v5.27.0-dev.5 [skip ci]
# [5.27.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.4...v5.27.0-dev.5) (2025-06-08)

### Features

* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([af827e2](af827e2f1a))
2025-06-08 07:17:04 +00:00
Nuckyz
af827e2f1a feat(Google Photos): Add Enable DCIM folders backup control patch (#5138) 2025-06-08 09:13:53 +02:00
semantic-release-bot
97cd31509e chore: Release v5.27.0-dev.4 [skip ci]
# [5.27.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.3...v5.27.0-dev.4) (2025-06-06)

### Bug Fixes

* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([c0448de](c0448dece4))
2025-06-06 21:22:31 +00:00
hoodles
c0448dece4 fix(Bandcamp - Remove play limits): Support latest app version (#5124) 2025-06-06 23:20:00 +02:00
semantic-release-bot
f00a95c0d8 chore: Release v5.27.0-dev.3 [skip ci]
# [5.27.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.2...v5.27.0-dev.3) (2025-06-06)

### Bug Fixes

* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([7a432e5](7a432e5741))
2025-06-06 21:14:18 +00:00
Nuckyz
7a432e5741 fix(Spotify): Hide Create button patch failing in edge cases (#5131) 2025-06-06 18:11:32 -03:00
semantic-release-bot
966a78bd81 chore: Release v5.27.0-dev.2 [skip ci]
# [5.27.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.1...v5.27.0-dev.2) (2025-06-06)

### Bug Fixes

* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([6aff8e8](6aff8e8ca4))
2025-06-06 07:06:49 +00:00
LisoUseInAIKyrios
6aff8e8ca4 fix(YouTube - Video quality): Remove non-functional Shorts 144p default quality 2025-06-06 09:04:07 +02:00
github-actions[bot]
11aa463fa6 chore: Sync translations (#5125) 2025-06-06 09:03:38 +02:00
semantic-release-bot
bf1b639a2f chore: Release v5.27.0-dev.1 [skip ci]
# [5.27.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.3...v5.27.0-dev.1) (2025-06-05)

### Features

* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([6d5380d](6d5380d44d))
2025-06-05 20:08:58 +00:00
LisoUseInAIKyrios
6d5380d44d feat(YouTube - Theme): Add option for black and white splash screen animation (#5119) 2025-06-05 22:06:12 +02:00
github-actions[bot]
7e1547b5b9 chore: Sync translations (#5123) 2025-06-05 22:05:56 +02:00
semantic-release-bot
c790b45cc5 chore: Release v5.26.1-dev.3 [skip ci]
## [5.26.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.2...v5.26.1-dev.3) (2025-06-05)

### Bug Fixes

* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([65fc6b4](65fc6b43f5))
2025-06-05 19:57:52 +00:00
Nuckyz
65fc6b43f5 fix(Spotify): Prevent hiding all navigation bar buttons (#5122) 2025-06-05 16:55:33 -03:00
232 changed files with 4250 additions and 3662 deletions

View File

@@ -1,3 +1,183 @@
## [5.28.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.1-dev.1...v5.28.1-dev.2) (2025-06-23)
### Bug Fixes
* **YouTube - Hide ads:** Hide new type of product ad in video description ([#5225](https://github.com/ReVanced/revanced-patches/issues/5225)) ([1e2efad](https://github.com/ReVanced/revanced-patches/commit/1e2efad7b2714c395ed6b0a77cbbf8a2265df520))
## [5.28.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.28.0...v5.28.1-dev.1) (2025-06-22)
### Bug Fixes
* Add scrollable content to modern style settings dialogs ([#5211](https://github.com/ReVanced/revanced-patches/issues/5211)) ([e6876d5](https://github.com/ReVanced/revanced-patches/commit/e6876d510d28f6a3a41ec1722a033b3e30a22c65))
# [5.28.0](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0) (2025-06-20)
### Bug Fixes
* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([a93d74d](https://github.com/ReVanced/revanced-patches/commit/a93d74d26e7ef87a3745df2b9fe82722d65a0e59))
* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([4ad4887](https://github.com/ReVanced/revanced-patches/commit/4ad488744d87543c31e453dc7b6d8182b3a7f440))
* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([ca83047](https://github.com/ReVanced/revanced-patches/commit/ca83047f5c4acbb267d5b98db80ad111999086e0))
* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([e7dd061](https://github.com/ReVanced/revanced-patches/commit/e7dd061c513af90861c0ab0d7adc6ee43be57ce2))
* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([3558c44](https://github.com/ReVanced/revanced-patches/commit/3558c44a05c13f19fefdbbf14b364181a79f17c0))
* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([c9e54e1](https://github.com/ReVanced/revanced-patches/commit/c9e54e1d36243945ac1ec3108fe38edf0e15d772))
### Features
* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([8736b6a](https://github.com/ReVanced/revanced-patches/commit/8736b6a80b48cb1f4562c9f9919804006ddb18bd))
* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([312b6dc](https://github.com/ReVanced/revanced-patches/commit/312b6dc04e01c2758cd304ca8606306027aa2f01))
# [5.28.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.7...v5.28.0-dev.8) (2025-06-19)
### Bug Fixes
* **Messenger - Remove Meta AI:** Improve patch logic ([#5153](https://github.com/ReVanced/revanced-patches/issues/5153)) ([4ad4887](https://github.com/ReVanced/revanced-patches/commit/4ad488744d87543c31e453dc7b6d8182b3a7f440))
# [5.28.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.6...v5.28.0-dev.7) (2025-06-18)
### Bug Fixes
* **YouTube:** Remove old app targets that are no longer supported by YouTube ([#5192](https://github.com/ReVanced/revanced-patches/issues/5192)) ([c9e54e1](https://github.com/ReVanced/revanced-patches/commit/c9e54e1d36243945ac1ec3108fe38edf0e15d772))
# [5.28.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.5...v5.28.0-dev.6) (2025-06-17)
### Bug Fixes
* **Threads - Hide ads:** Constrain patch to the last working app target ([#5189](https://github.com/ReVanced/revanced-patches/issues/5189)) ([3558c44](https://github.com/ReVanced/revanced-patches/commit/3558c44a05c13f19fefdbbf14b364181a79f17c0))
# [5.28.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.4...v5.28.0-dev.5) (2025-06-17)
### Bug Fixes
* **Pandora - Disable ads:** Support latest app target ([#5185](https://github.com/ReVanced/revanced-patches/issues/5185)) ([ca83047](https://github.com/ReVanced/revanced-patches/commit/ca83047f5c4acbb267d5b98db80ad111999086e0))
# [5.28.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.3...v5.28.0-dev.4) (2025-06-13)
### Features
* Use modern style settings dialogs ([#5109](https://github.com/ReVanced/revanced-patches/issues/5109)) ([312b6dc](https://github.com/ReVanced/revanced-patches/commit/312b6dc04e01c2758cd304ca8606306027aa2f01))
# [5.28.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.2...v5.28.0-dev.3) (2025-06-11)
### Bug Fixes
* **Spotify:** Fix `Hide Create button` and `Sanitize sharing links` for older but supported app targets ([#5159](https://github.com/ReVanced/revanced-patches/issues/5159)) ([e7dd061](https://github.com/ReVanced/revanced-patches/commit/e7dd061c513af90861c0ab0d7adc6ee43be57ce2))
# [5.28.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.28.0-dev.1...v5.28.0-dev.2) (2025-06-11)
### Bug Fixes
* **Google Photos:** Resolve startup crash if MicroG GmsCore does not already have granted permissions ([a93d74d](https://github.com/ReVanced/revanced-patches/commit/a93d74d26e7ef87a3745df2b9fe82722d65a0e59))
# [5.28.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.27.0...v5.28.0-dev.1) (2025-06-11)
### Features
* **Spotify:** Add `Change lyrics provider` patch ([#4937](https://github.com/ReVanced/revanced-patches/issues/4937)) ([8736b6a](https://github.com/ReVanced/revanced-patches/commit/8736b6a80b48cb1f4562c9f9919804006ddb18bd))
# [5.27.0](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.27.0) (2025-06-09)
### Bug Fixes
* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([863e92b](https://github.com/ReVanced/revanced-patches/commit/863e92b20ad6682f10524e475ed18f879048ecae))
* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([0923600](https://github.com/ReVanced/revanced-patches/commit/0923600739a126329fc62100b500216860d7005e))
* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([8afbef0](https://github.com/ReVanced/revanced-patches/commit/8afbef01343c1e3e6e7e4a4cec6319aebfa4b11c))
* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([9a6a639](https://github.com/ReVanced/revanced-patches/commit/9a6a639c4905b00d6dffb0923c839c8e3ae54d0c))
* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([bef0dac](https://github.com/ReVanced/revanced-patches/commit/bef0dacac54caf1ca9511d7bc19b19140ccb4eaf))
* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([3113cd6](https://github.com/ReVanced/revanced-patches/commit/3113cd6d092952c8657454452f34c0ae85358ec9))
### Features
* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([92311b8](https://github.com/ReVanced/revanced-patches/commit/92311b8e5675f3d4b80ed690d34b699fb847e3cd))
* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([328d232](https://github.com/ReVanced/revanced-patches/commit/328d232fe77406fa93a14768fc66e7b998506fba))
* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([9175b23](https://github.com/ReVanced/revanced-patches/commit/9175b23e8360d13c8c1c9c8602ca0b5931d13627))
* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([dd8afa2](https://github.com/ReVanced/revanced-patches/commit/dd8afa2b07b50be24d764c0f6ddc9e1bbdb91bf1))
* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([ac6b916](https://github.com/ReVanced/revanced-patches/commit/ac6b916c0c212167c4645e2110500dc811b3e54a))
* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([42db0c2](https://github.com/ReVanced/revanced-patches/commit/42db0c2e36fefccdbeaa072edcec48b1e05b6270))
# [5.27.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.8...v5.27.0-dev.9) (2025-06-09)
### Features
* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([9175b23](https://github.com/ReVanced/revanced-patches/commit/9175b23e8360d13c8c1c9c8602ca0b5931d13627))
# [5.27.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.7...v5.27.0-dev.8) (2025-06-09)
### Features
* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([92311b8](https://github.com/ReVanced/revanced-patches/commit/92311b8e5675f3d4b80ed690d34b699fb847e3cd))
# [5.27.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.6...v5.27.0-dev.7) (2025-06-08)
### Features
* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([dd8afa2](https://github.com/ReVanced/revanced-patches/commit/dd8afa2b07b50be24d764c0f6ddc9e1bbdb91bf1))
# [5.27.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.5...v5.27.0-dev.6) (2025-06-08)
### Features
* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([ac6b916](https://github.com/ReVanced/revanced-patches/commit/ac6b916c0c212167c4645e2110500dc811b3e54a))
# [5.27.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.4...v5.27.0-dev.5) (2025-06-08)
### Features
* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([328d232](https://github.com/ReVanced/revanced-patches/commit/328d232fe77406fa93a14768fc66e7b998506fba))
# [5.27.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.3...v5.27.0-dev.4) (2025-06-06)
### Bug Fixes
* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([863e92b](https://github.com/ReVanced/revanced-patches/commit/863e92b20ad6682f10524e475ed18f879048ecae))
# [5.27.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.2...v5.27.0-dev.3) (2025-06-06)
### Bug Fixes
* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([0923600](https://github.com/ReVanced/revanced-patches/commit/0923600739a126329fc62100b500216860d7005e))
# [5.27.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.1...v5.27.0-dev.2) (2025-06-06)
### Bug Fixes
* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([3113cd6](https://github.com/ReVanced/revanced-patches/commit/3113cd6d092952c8657454452f34c0ae85358ec9))
# [5.27.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.3...v5.27.0-dev.1) (2025-06-05)
### Features
* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([42db0c2](https://github.com/ReVanced/revanced-patches/commit/42db0c2e36fefccdbeaa072edcec48b1e05b6270))
## [5.26.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.2...v5.26.1-dev.3) (2025-06-05)
### Bug Fixes
* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([8afbef0](https://github.com/ReVanced/revanced-patches/commit/8afbef01343c1e3e6e7e4a4cec6319aebfa4b11c))
## [5.26.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.1...v5.26.1-dev.2) (2025-06-05) ## [5.26.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.1...v5.26.1-dev.2) (2025-06-05)

View File

@@ -1,14 +1,24 @@
package app.revanced.extension.messenger.metaai; package app.revanced.extension.messenger.metaai;
import java.util.*;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class RemoveMetaAIPatch { public class RemoveMetaAIPatch {
public static boolean overrideConfigBool(long id, boolean value) { private static final Set<Long> loggedIDs = Collections.synchronizedSet(new HashSet<>());
// It seems like all configs starting with 363219 are related to Meta AI.
// A list of specific ones that need disabling would probably be better, public static boolean overrideBooleanFlag(long id, boolean value) {
// but these config numbers seem to change slightly with each update. try {
// These first 6 digits don't though. if (Long.toString(id).startsWith("REPLACED_BY_PATCH")) {
if (Long.toString(id).startsWith("363219")) if (loggedIDs.add(id))
return false; Logger.printInfo(() -> "Overriding " + id + " from " + value + " to false");
return false;
}
} catch (Exception ex) {
Logger.printException(() -> "overrideBooleanFlag failure", ex);
}
return value; return value;
} }

View File

@@ -5,7 +5,7 @@ import static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.app.SearchManager; import android.app.SearchManager;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
@@ -15,6 +15,8 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.PowerManager; import android.os.PowerManager;
import android.provider.Settings; import android.provider.Settings;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
@@ -26,6 +28,7 @@ import java.util.Locale;
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.Utils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class GmsCoreSupport { public class GmsCoreSupport {
@@ -78,13 +81,27 @@ public class GmsCoreSupport {
// Use a delay to allow the activity to finish initializing. // Use a delay to allow the activity to finish initializing.
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme. // Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
Utils.runOnMainThreadDelayed(() -> { Utils.runOnMainThreadDelayed(() -> {
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
str("gms_core_dialog_title"), // Title.
str(dialogMessageRef), // Message.
null, // No EditText.
str(positiveButtonTextRef), // OK button text.
() -> onPositiveClickListener.onClick(null, 0), // Convert DialogInterface.OnClickListener to Runnable.
null, // No Cancel button action.
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
Dialog dialog = dialogPair.first;
// Do not set cancelable to false, to allow using back button to skip the action, // Do not set cancelable to false, to allow using back button to skip the action,
// just in case the battery change can never be satisfied. // just in case the battery change can never be satisfied.
var dialog = new AlertDialog.Builder(context) dialog.setCancelable(true);
.setTitle(str("gms_core_dialog_title"))
.setMessage(str(dialogMessageRef)) // Show the dialog
.setPositiveButton(str(positiveButtonTextRef), onPositiveClickListener)
.create();
Utils.showDialog(context, dialog); Utils.showDialog(context, dialog);
}, 100); }, 100);
} }

View File

@@ -19,7 +19,8 @@ import app.revanced.extension.shared.settings.preference.LogBufferManager;
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB), * ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
* and additionally accessible thru {@link LogBufferManager}. * and additionally accessible thru {@link LogBufferManager}.
* *
* All methods are thread safe. * All methods are thread safe, and are safe to call even
* if {@link Utils#getContext()} is not available.
*/ */
public class Logger { public class Logger {
@@ -138,6 +139,20 @@ public class Logger {
} }
} }
private static boolean shouldLogDebug() {
// If the app is still starting up and the context is not yet set,
// then allow debug logging regardless what the debug setting actually is.
return Utils.context == null || DEBUG.get();
}
private static boolean shouldShowErrorToast() {
return Utils.context != null && DEBUG_TOAST_ON_ERROR.get();
}
private static boolean includeStackTrace() {
return Utils.context != null && DEBUG_STACKTRACE.get();
}
/** /**
* Logs debug messages under the outer class name of the code calling this method. * Logs debug messages under the outer class name of the code calling this method.
* <p> * <p>
@@ -157,8 +172,8 @@ public class Logger {
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled. * building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
*/ */
public static void printDebug(LogMessage message, @Nullable Exception ex) { public static void printDebug(LogMessage message, @Nullable Exception ex) {
if (DEBUG.get()) { if (shouldLogDebug()) {
logInternal(LogLevel.DEBUG, message, ex, DEBUG_STACKTRACE.get(), false); logInternal(LogLevel.DEBUG, message, ex, includeStackTrace(), false);
} }
} }
@@ -173,7 +188,7 @@ public class Logger {
* Logs information messages using the outer class name of the code calling this method. * Logs information messages using the outer class name of the code calling this method.
*/ */
public static void printInfo(LogMessage message, @Nullable Exception ex) { public static void printInfo(LogMessage message, @Nullable Exception ex) {
logInternal(LogLevel.INFO, message, ex, DEBUG_STACKTRACE.get(), false); logInternal(LogLevel.INFO, message, ex, includeStackTrace(), false);
} }
/** /**
@@ -194,22 +209,6 @@ public class Logger {
* @param ex exception (optional) * @param ex exception (optional)
*/ */
public static void printException(LogMessage message, @Nullable Throwable ex) { public static void printException(LogMessage message, @Nullable Throwable ex) {
logInternal(LogLevel.ERROR, message, ex, DEBUG_STACKTRACE.get(), DEBUG_TOAST_ON_ERROR.get()); logInternal(LogLevel.ERROR, message, ex, includeStackTrace(), shouldShowErrorToast());
}
/**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used.
*/
public static void initializationInfo(LogMessage message) {
logInternal(LogLevel.INFO, message, null, false, false);
}
/**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used.
*/
public static void initializationException(LogMessage message, @Nullable Exception ex) {
logInternal(LogLevel.ERROR, message, ex, false, false);
} }
} }

View File

@@ -3,15 +3,21 @@ package app.revanced.extension.shared.checks;
import static android.text.Html.FROM_HTML_MODE_COMPACT; import static android.text.Html.FROM_HTML_MODE_COMPACT;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction; import static app.revanced.extension.shared.Utils.DialogFragmentOnStartAction;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.graphics.PorterDuff;
import android.net.Uri; import android.net.Uri;
import android.text.Html; import android.text.Html;
import android.util.Pair;
import android.view.Gravity;
import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -86,38 +92,58 @@ abstract class Check {
); );
Utils.runOnMainThreadDelayed(() -> { Utils.runOnMainThreadDelayed(() -> {
AlertDialog alert = new AlertDialog.Builder(activity) // Create the custom dialog.
.setCancelable(false) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setIconAttribute(android.R.attr.alertDialogIcon) activity,
.setTitle(str("revanced_check_environment_failed_title")) str("revanced_check_environment_failed_title"), // Title.
.setMessage(message) message, // Message.
.setPositiveButton( null, // No EditText.
" ", str("revanced_check_environment_dialog_open_official_source_button"), // OK button text.
(dialog, which) -> { () -> {
final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE); // Action for the OK (website) button.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final var intent = new Intent(Intent.ACTION_VIEW, GOOD_SOURCE);
activity.startActivity(intent); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
activity.startActivity(intent);
// Shutdown to prevent the user from navigating back to this app, // Shutdown to prevent the user from navigating back to this app,
// which is no longer showing a warning dialog. // which is no longer showing a warning dialog.
activity.finishAffinity(); activity.finishAffinity();
System.exit(0); System.exit(0);
} },
).setNegativeButton( null, // No cancel button.
" ", str("revanced_check_environment_dialog_ignore_button"), // Neutral button text.
(dialog, which) -> { () -> {
// Cleanup data if the user incorrectly imported a huge negative number. // Neutral button action.
final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get()); // Cleanup data if the user incorrectly imported a huge negative number.
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1); final int current = Math.max(0, BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.get());
BaseSettings.CHECK_ENVIRONMENT_WARNINGS_ISSUED.save(current + 1);
},
true // Dismiss dialog when onNeutralClick.
);
dialog.dismiss(); // Get the dialog and main layout.
} Dialog dialog = dialogPair.first;
).create(); LinearLayout mainLayout = dialogPair.second;
Utils.showDialog(activity, alert, false, new DialogFragmentOnStartAction() { // Add icon to the dialog.
ImageView iconView = new ImageView(activity);
iconView.setImageResource(Utils.getResourceIdentifier("revanced_ic_dialog_alert", "drawable"));
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
iconView.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
iconParams.gravity = Gravity.CENTER;
mainLayout.addView(iconView, 0); // Add icon at the top.
dialog.setCancelable(false);
// Show the dialog.
Utils.showDialog(activity, dialog, false, new DialogFragmentOnStartAction() {
boolean hasRun; boolean hasRun;
@Override @Override
public void onStart(AlertDialog dialog) { public void onStart(Dialog dialog) {
// Only run this once, otherwise if the user changes to a different app // Only run this once, otherwise if the user changes to a different app
// then changes back, this handler will run again and disable the buttons. // then changes back, this handler will run again and disable the buttons.
if (hasRun) { if (hasRun) {
@@ -125,19 +151,43 @@ abstract class Check {
} }
hasRun = true; hasRun = true;
var openWebsiteButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); // Get the button container to access buttons.
LinearLayout buttonContainer = (LinearLayout) mainLayout.getChildAt(mainLayout.getChildCount() - 1);
Button openWebsiteButton;
Button ignoreButton;
// Check if buttons are in a single-row layout (buttonContainer has one child: rowContainer).
if (buttonContainer.getChildCount() == 1 && buttonContainer.getChildAt(0) instanceof LinearLayout) {
LinearLayout rowContainer = (LinearLayout) buttonContainer.getChildAt(0);
// Neutral button is the first child (index 0).
ignoreButton = (Button) rowContainer.getChildAt(0);
// OK button is the last child.
openWebsiteButton = (Button) rowContainer.getChildAt(rowContainer.getChildCount() - 1);
} else {
// Multi-row layout: buttons are in separate containers, ordered OK, Cancel, Neutral.
LinearLayout okContainer =
(LinearLayout) buttonContainer.getChildAt(0); // OK is first.
openWebsiteButton = (Button) okContainer.getChildAt(0);
LinearLayout neutralContainer =
(LinearLayout)buttonContainer.getChildAt(buttonContainer.getChildCount() - 1); // Neutral is last.
ignoreButton = (Button) neutralContainer.getChildAt(0);
}
// Initially set buttons to INVISIBLE and disabled.
openWebsiteButton.setVisibility(View.INVISIBLE);
openWebsiteButton.setEnabled(false); openWebsiteButton.setEnabled(false);
ignoreButton.setVisibility(View.INVISIBLE);
ignoreButton.setEnabled(false);
var dismissButton = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); // Start the countdown for showing and enabling buttons.
dismissButton.setEnabled(false); getCountdownRunnable(ignoreButton, openWebsiteButton).run();
getCountdownRunnable(dismissButton, openWebsiteButton).run();
} }
}); });
}, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs. }, 1000); // Use a delay, so this dialog is shown on top of any other startup dialogs.
} }
private static Runnable getCountdownRunnable(Button dismissButton, Button openWebsiteButton) { private static Runnable getCountdownRunnable(Button ignoreButton, Button openWebsiteButton) {
return new Runnable() { return new Runnable() {
private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON; private int secondsRemaining = SECONDS_BEFORE_SHOWING_IGNORE_BUTTON;
@@ -146,17 +196,15 @@ abstract class Check {
Utils.verifyOnMainThread(); Utils.verifyOnMainThread();
if (secondsRemaining > 0) { if (secondsRemaining > 0) {
if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON == 0) { if (secondsRemaining - SECONDS_BEFORE_SHOWING_WEBSITE_BUTTON <= 0) {
openWebsiteButton.setText(str("revanced_check_environment_dialog_open_official_source_button")); openWebsiteButton.setVisibility(View.VISIBLE);
openWebsiteButton.setEnabled(true); openWebsiteButton.setEnabled(true);
} }
secondsRemaining--; secondsRemaining--;
Utils.runOnMainThreadDelayed(this, 1000); Utils.runOnMainThreadDelayed(this, 1000);
} else { } else {
dismissButton.setText(str("revanced_check_environment_dialog_ignore_button")); ignoreButton.setVisibility(View.VISIBLE);
dismissButton.setEnabled(true); ignoreButton.setEnabled(true);
} }
} }
}; };

View File

@@ -52,7 +52,7 @@ public class Route {
private int countMatches(CharSequence seq, char c) { private int countMatches(CharSequence seq, char c) {
int count = 0; int count = 0;
for (int i = 0; i < seq.length(); i++) { for (int i = 0, length = seq.length(); i < length; i++) {
if (seq.charAt(i) == c) if (seq.charAt(i) == c)
count++; count++;
} }

View File

@@ -3,12 +3,20 @@ package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.*; import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceGroup;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.preference.SwitchPreference;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -44,7 +52,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* Set by subclasses if Strings cannot be added as a resource. * Set by subclasses if Strings cannot be added as a resource.
*/ */
@Nullable @Nullable
protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle; protected static String restartDialogButtonText, restartDialogTitle, confirmDialogTitle, restartDialogMessage;
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
try { try {
@@ -76,7 +84,7 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
updatingPreference = true; updatingPreference = true;
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'. // Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
// Updating here can can cause a recursive call back into this same method. // Updating here can cause a recursive call back into this same method.
updatePreference(pref, setting, true, settingImportInProgress); updatePreference(pref, setting, true, settingImportInProgress);
// Update any other preference availability that may now be different. // Update any other preference availability that may now be different.
updateUIAvailability(); updateUIAvailability();
@@ -116,11 +124,14 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
showingUserDialogMessage = true; showingUserDialogMessage = true;
new AlertDialog.Builder(context) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(confirmDialogTitle) context,
.setMessage(Objects.requireNonNull(setting.userDialogMessage).toString()) confirmDialogTitle, // Title.
.setPositiveButton(android.R.string.ok, (dialog, id) -> { Objects.requireNonNull(setting.userDialogMessage).toString(), // No message.
// User confirmed, save to the Setting. null, // No EditText.
null, // OK button text.
() -> {
// OK button action. User confirmed, save to the Setting.
updatePreference(pref, setting, true, false); updatePreference(pref, setting, true, false);
// Update availability of other preferences that may be changed. // Update availability of other preferences that may be changed.
@@ -129,23 +140,27 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (setting.rebootApp) { if (setting.rebootApp) {
showRestartDialog(context); showRestartDialog(context);
} }
}) },
.setNegativeButton(android.R.string.cancel, (dialog, id) -> { () -> {
// Restore whatever the setting was before the change. // Cancel button action. Restore whatever the setting was before the change.
updatePreference(pref, setting, true, true); updatePreference(pref, setting, true, true);
}) },
.setOnDismissListener(dialog -> { null, // No Neutral button.
showingUserDialogMessage = false; null, // No Neutral button action.
}) true // Dismiss dialog when onNeutralClick.
.setCancelable(false) );
.show();
dialogPair.first.setOnDismissListener(d -> showingUserDialogMessage = false);
// Show the dialog.
dialogPair.first.show();
} }
/** /**
* Updates all Preferences values and their availability using the current values in {@link Setting}. * Updates all Preferences values and their availability using the current values in {@link Setting}.
*/ */
protected void updateUIToSettingValues() { protected void updateUIToSettingValues() {
updatePreferenceScreen(getPreferenceScreen(), true,true); updatePreferenceScreen(getPreferenceScreen(), true, true);
} }
/** /**
@@ -280,17 +295,27 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
if (restartDialogTitle == null) { if (restartDialogTitle == null) {
restartDialogTitle = str("revanced_settings_restart_title"); restartDialogTitle = str("revanced_settings_restart_title");
} }
if (restartDialogMessage == null) {
restartDialogMessage = str("revanced_settings_restart_dialog_message");
}
if (restartDialogButtonText == null) { if (restartDialogButtonText == null) {
restartDialogButtonText = str("revanced_settings_restart"); restartDialogButtonText = str("revanced_settings_restart");
} }
new AlertDialog.Builder(context) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(context,
.setMessage(restartDialogTitle) restartDialogTitle, // Title.
.setPositiveButton(restartDialogButtonText, (dialog, id) restartDialogMessage, // Message.
-> Utils.restartApp(context)) null, // No EditText.
.setNegativeButton(android.R.string.cancel, null) restartDialogButtonText, // OK button text.
.setCancelable(false) () -> Utils.restartApp(context), // OK button action.
.show(); () -> {}, // Cancel button action (dismiss only).
null, // No Neutral button text.
null, // No Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} }
@SuppressLint("ResourceType") @SuppressLint("ResourceType")

View File

@@ -2,8 +2,9 @@ package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier; import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
@@ -18,14 +19,12 @@ import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan; import android.text.style.RelativeSizeSpan;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent; import android.view.ViewParent;
import android.widget.Button; import android.widget.*;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
@@ -182,7 +181,7 @@ public class ColorPickerPreference extends EditTextPreference {
* @throws IllegalArgumentException If the color string is invalid. * @throws IllegalArgumentException If the color string is invalid.
*/ */
@Override @Override
public final void setText(String colorString) { public final void setText(String colorString) {
try { try {
Logger.printDebug(() -> "setText: " + colorString); Logger.printDebug(() -> "setText: " + colorString);
super.setText(colorString); super.setText(colorString);
@@ -216,86 +215,6 @@ public class ColorPickerPreference extends EditTextPreference {
widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA); widgetColorDot.setAlpha(isEnabled() ? 1.0f : DISABLED_ALPHA);
} }
/**
* Creates a layout with a color preview and EditText for hex color input.
*
* @param context The context for creating the layout.
* @return A LinearLayout containing the color preview and EditText.
*/
private LinearLayout createDialogLayout(Context context) {
LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(70, 0, 70, 0);
// Inflate color picker.
View colorPicker = LayoutInflater.from(context).inflate(
getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPicker.findViewById(
getResourceIdentifier("color_picker_view", "id"));
dialogColorPickerView.setColor(currentColor);
layout.addView(colorPicker);
// Horizontal layout for preview and EditText.
LinearLayout inputLayout = new LinearLayout(context);
inputLayout.setOrientation(LinearLayout.HORIZONTAL);
inputLayout.setPadding(0, 20, 0, 0);
dialogColorPreview = new TextView(context);
inputLayout.addView(dialogColorPreview);
updateColorPreview();
EditText editText = getEditText();
ViewParent parent = editText.getParent();
if (parent instanceof ViewGroup parentViewGroup) {
parentViewGroup.removeView(editText);
}
editText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
String currentColorString = getColorString(currentColor);
editText.setText(currentColorString);
editText.setSelection(currentColorString.length());
editText.setTypeface(Typeface.MONOSPACE);
colorTextWatcher = createColorTextWatcher(dialogColorPickerView);
editText.addTextChangedListener(colorTextWatcher);
inputLayout.addView(editText);
// Add a dummy view to take up remaining horizontal space,
// otherwise it will show an oversize underlined text view.
View paddingView = new View(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.MATCH_PARENT,
1f
);
paddingView.setLayoutParams(params);
inputLayout.addView(paddingView);
layout.addView(inputLayout);
// Set up color picker listener with debouncing.
// Add listener last to prevent callbacks from set calls above.
dialogColorPickerView.setOnColorChangedListener(color -> {
// Check if it actually changed, since this callback
// can be caused by updates in afterTextChanged().
if (currentColor == color) {
return;
}
String updatedColorString = getColorString(color);
Logger.printDebug(() -> "onColorChanged: " + updatedColorString);
currentColor = color;
editText.setText(updatedColorString);
editText.setSelection(updatedColorString.length());
updateColorPreview();
updateWidgetColorDot();
});
return layout;
}
/** /**
* Updates the color preview TextView with a colored dot. * Updates the color preview TextView with a colored dot.
*/ */
@@ -360,65 +279,153 @@ public class ColorPickerPreference extends EditTextPreference {
} }
/** /**
* Prepares the dialog builder with a custom view and reset button. * Creates a Dialog with a color preview and EditText for hex color input.
*
* @param builder The AlertDialog.Builder to configure.
*/ */
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
Utils.setEditTextDialogTheme(builder);
LinearLayout dialogLayout = createDialogLayout(builder.getContext());
builder.setView(dialogLayout);
final int originalColor = currentColor;
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
try {
String colorString = getEditText().getText().toString();
if (colorString.length() != COLOR_STRING_LENGTH) {
Utils.showToastShort(str("revanced_settings_color_invalid"));
setText(getColorString(originalColor));
return;
}
setText(colorString);
} catch (Exception ex) {
// Should never happen due to a bad color string,
// since the text is validated and fixed while the user types.
Logger.printException(() -> "setPositiveButton failure", ex);
}
});
builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
try {
// Restore the original color.
setText(getColorString(originalColor));
} catch (Exception ex) {
Logger.printException(() -> "setNegativeButton failure", ex);
}
});
}
@Override @Override
protected void showDialog(Bundle state) { protected void showDialog(Bundle state) {
super.showDialog(state); Context context = getContext();
AlertDialog dialog = (AlertDialog) getDialog(); // Inflate color picker view.
dialog.setCanceledOnTouchOutside(false); View colorPicker = LayoutInflater.from(context).inflate(
getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPicker.findViewById(
getResourceIdentifier("revanced_color_picker_view", "id"));
dialogColorPickerView.setColor(currentColor);
// Do not close dialog when reset is pressed. // Horizontal layout for preview and EditText.
Button button = dialog.getButton(AlertDialog.BUTTON_NEUTRAL); LinearLayout inputLayout = new LinearLayout(context);
button.setOnClickListener(view -> { inputLayout.setOrientation(LinearLayout.HORIZONTAL);
try {
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF; dialogColorPreview = new TextView(context);
// Setting view color causes listener callback into this class. LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(
dialogColorPickerView.setColor(defaultColor); LinearLayout.LayoutParams.WRAP_CONTENT,
} catch (Exception ex) { LinearLayout.LayoutParams.WRAP_CONTENT
Logger.printException(() -> "setOnClickListener failure", ex); );
previewParams.setMargins(dipToPixels(15), 0, dipToPixels(10), 0); // text dot has its own indents so 15, instead 16.
dialogColorPreview.setLayoutParams(previewParams);
inputLayout.addView(dialogColorPreview);
updateColorPreview();
EditText editText = getEditText();
ViewParent parent = editText.getParent();
if (parent instanceof ViewGroup parentViewGroup) {
parentViewGroup.removeView(editText);
}
editText.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
String currentColorString = getColorString(currentColor);
editText.setText(currentColorString);
editText.setSelection(currentColorString.length());
editText.setTypeface(Typeface.MONOSPACE);
colorTextWatcher = createColorTextWatcher(dialogColorPickerView);
editText.addTextChangedListener(colorTextWatcher);
inputLayout.addView(editText);
// Add a dummy view to take up remaining horizontal space,
// otherwise it will show an oversize underlined text view.
View paddingView = new View(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0,
LinearLayout.LayoutParams.MATCH_PARENT,
1f
);
paddingView.setLayoutParams(params);
inputLayout.addView(paddingView);
// Create content container for color picker and input layout.
LinearLayout contentContainer = new LinearLayout(context);
contentContainer.setOrientation(LinearLayout.VERTICAL);
contentContainer.addView(colorPicker);
contentContainer.addView(inputLayout);
// Create ScrollView to wrap the content container.
ScrollView contentScrollView = new ScrollView(context);
contentScrollView.setVerticalScrollBarEnabled(false); // Disable vertical scrollbar.
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); // Disable overscroll effect.
LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1.0f
);
contentScrollView.setLayoutParams(scrollViewParams);
contentScrollView.addView(contentContainer);
// Create custom dialog.
final int originalColor = currentColor & 0x00FFFFFF;
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : str("revanced_settings_color_picker_title"), // Title.
null, // No message.
null, // No EditText.
null, // OK button text.
() -> {
// OK button action.
try {
String colorString = editText.getText().toString();
if (colorString.length() != COLOR_STRING_LENGTH) {
Utils.showToastShort(str("revanced_settings_color_invalid"));
setText(getColorString(originalColor));
return;
}
setText(colorString);
} catch (Exception ex) {
// Should never happen due to a bad color string,
// since the text is validated and fixed while the user types.
Logger.printException(() -> "OK button failure", ex);
}
},
() -> {
// Cancel button action.
try {
// Restore the original color.
setText(getColorString(originalColor));
} catch (Exception ex) {
Logger.printException(() -> "Cancel button failure", ex);
}
},
str("revanced_settings_reset_color"), // Neutral button text.
() -> {
// Neutral button action.
try {
final int defaultColor = Color.parseColor(colorSetting.defaultValue) & 0x00FFFFFF;
// Setting view color causes listener callback into this class.
dialogColorPickerView.setColor(defaultColor);
} catch (Exception ex) {
Logger.printException(() -> "Reset button failure", ex);
}
},
false // Do not dismiss dialog when onNeutralClick.
);
// Add the ScrollView to the dialog's main layout.
LinearLayout dialogMainLayout = dialogPair.second;
dialogMainLayout.addView(contentScrollView, dialogMainLayout.getChildCount() - 1);
// Set up color picker listener with debouncing.
// Add listener last to prevent callbacks from set calls above.
dialogColorPickerView.setOnColorChangedListener(color -> {
// Check if it actually changed, since this callback
// can be caused by updates in afterTextChanged().
if (currentColor == color) {
return;
} }
String updatedColorString = getColorString(color);
Logger.printDebug(() -> "onColorChanged: " + updatedColorString);
currentColor = color;
editText.setText(updatedColorString);
editText.setSelection(updatedColorString.length());
updateColorPreview();
updateWidgetColorDot();
}); });
// Configure and show the dialog.
Dialog dialog = dialogPair.first;
dialog.setCanceledOnTouchOutside(false);
dialog.show();
} }
@Override @Override

View File

@@ -29,8 +29,8 @@ import app.revanced.extension.shared.Utils;
* <p> * <p>
* This view displays two main components for color selection: * This view displays two main components for color selection:
* <ul> * <ul>
* <li><b>Hue Bar:</b> A vertical bar on the right that allows the user to select the hue component of the color. * <li><b>Hue Bar:</b> A horizontal bar at the bottom that allows the user to select the hue component of the color.
* <li><b>Saturation-Value Selector:</b> A rectangular area that allows the user to select the saturation and value (brightness) * <li><b>Saturation-Value Selector:</b> A rectangular area above the hue bar that allows the user to select the saturation and value (brightness)
* components of the color based on the selected hue. * components of the color based on the selected hue.
* </ul> * </ul>
* *
@@ -63,12 +63,12 @@ public class ColorPickerView extends View {
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24); private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24);
private static final float VIEW_PADDING = dipToPixels(16); private static final float VIEW_PADDING = dipToPixels(16);
private static final float HUE_BAR_WIDTH = dipToPixels(12); private static final float HUE_BAR_HEIGHT = dipToPixels(12);
private static final float HUE_CORNER_RADIUS = dipToPixels(6); private static final float HUE_CORNER_RADIUS = dipToPixels(6);
private static final float SELECTOR_RADIUS = dipToPixels(12); private static final float SELECTOR_RADIUS = dipToPixels(12);
private static final float SELECTOR_STROKE_WIDTH = 8; private static final float SELECTOR_STROKE_WIDTH = 8;
/** /**
* Hue fill radius. Use slightly smaller radius for the selector handle fill, * Hue fill radius. Use slightly smaller radius for the selector handle fill,
* otherwise the anti-aliasing causes the fill color to bleed past the selector outline. * otherwise the anti-aliasing causes the fill color to bleed past the selector outline.
*/ */
private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2; private static final float SELECTOR_FILL_RADIUS = SELECTOR_RADIUS - SELECTOR_STROKE_WIDTH / 2;
@@ -144,17 +144,17 @@ public class ColorPickerView extends View {
final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8 final float DESIRED_ASPECT_RATIO = 0.8f; // height = width * 0.8
final int minWidth = Utils.dipToPixels(250); final int minWidth = Utils.dipToPixels(250);
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO); final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS);
int width = resolveSize(minWidth, widthMeasureSpec); int width = resolveSize(minWidth, widthMeasureSpec);
int height = resolveSize(minHeight, heightMeasureSpec); int height = resolveSize(minHeight, heightMeasureSpec);
// Ensure minimum dimensions for usability // Ensure minimum dimensions for usability.
width = Math.max(width, minWidth); width = Math.max(width, minWidth);
height = Math.max(height, minHeight); height = Math.max(height, minHeight);
// Adjust height to maintain desired aspect ratio if possible // Adjust height to maintain desired aspect ratio if possible.
final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO); final int desiredHeight = (int) (width * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS);
if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) { if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
height = desiredHeight; height = desiredHeight;
} }
@@ -171,22 +171,22 @@ public class ColorPickerView extends View {
protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
super.onSizeChanged(width, height, oldWidth, oldHeight); super.onSizeChanged(width, height, oldWidth, oldHeight);
// Calculate bounds with hue bar on the right // Calculate bounds with hue bar at the bottom.
final float effectiveWidth = width - (2 * VIEW_PADDING); final float effectiveWidth = width - (2 * VIEW_PADDING);
final float selectorWidth = effectiveWidth - HUE_BAR_WIDTH - MARGIN_BETWEEN_AREAS; final float effectiveHeight = height - (2 * VIEW_PADDING) - HUE_BAR_HEIGHT - MARGIN_BETWEEN_AREAS;
// Adjust rectangles to account for padding and density-independent dimensions // Adjust rectangles to account for padding and density-independent dimensions.
saturationValueRect.set( saturationValueRect.set(
VIEW_PADDING, VIEW_PADDING,
VIEW_PADDING, VIEW_PADDING,
VIEW_PADDING + selectorWidth, VIEW_PADDING + effectiveWidth,
height - VIEW_PADDING VIEW_PADDING + effectiveHeight
); );
hueRect.set( hueRect.set(
width - VIEW_PADDING - HUE_BAR_WIDTH,
VIEW_PADDING, VIEW_PADDING,
width - VIEW_PADDING, height - VIEW_PADDING - HUE_BAR_HEIGHT,
VIEW_PADDING + effectiveWidth,
height - VIEW_PADDING height - VIEW_PADDING
); );
@@ -201,7 +201,7 @@ public class ColorPickerView extends View {
private void updateHueShader() { private void updateHueShader() {
LinearGradient hueShader = new LinearGradient( LinearGradient hueShader = new LinearGradient(
hueRect.left, hueRect.top, hueRect.left, hueRect.top,
hueRect.left, hueRect.bottom, hueRect.right, hueRect.top,
HUE_COLORS, HUE_COLORS,
null, null,
Shader.TileMode.CLAMP Shader.TileMode.CLAMP
@@ -263,8 +263,8 @@ public class ColorPickerView extends View {
// Draw the hue bar. // Draw the hue bar.
canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint); canvas.drawRoundRect(hueRect, HUE_CORNER_RADIUS, HUE_CORNER_RADIUS, huePaint);
final float hueSelectorX = hueRect.centerX(); final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width();
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height(); final float hueSelectorY = hueRect.centerY();
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width(); final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height(); final float satSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
@@ -316,17 +316,17 @@ public class ColorPickerView extends View {
// Define touch expansion for the hue bar. // Define touch expansion for the hue bar.
RectF expandedHueRect = new RectF( RectF expandedHueRect = new RectF(
hueRect.left - TOUCH_EXPANSION, hueRect.left,
hueRect.top, hueRect.top - TOUCH_EXPANSION,
hueRect.right + TOUCH_EXPANSION, hueRect.right,
hueRect.bottom hueRect.bottom + TOUCH_EXPANSION
); );
switch (action) { switch (action) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
// Calculate current handle positions. // Calculate current handle positions.
final float hueSelectorX = hueRect.centerX(); final float hueSelectorX = hueRect.left + (hue / 360f) * hueRect.width();
final float hueSelectorY = hueRect.top + (hue / 360f) * hueRect.height(); final float hueSelectorY = hueRect.centerY();
final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width(); final float satSelectorX = saturationValueRect.left + saturation * saturationValueRect.width();
final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height(); final float valSelectorY = saturationValueRect.top + (1 - value) * saturationValueRect.height();
@@ -348,14 +348,14 @@ public class ColorPickerView extends View {
// Check if the touch started on a handle or within the expanded hue bar area. // Check if the touch started on a handle or within the expanded hue bar area.
if (hueHitRect.contains(x, y)) { if (hueHitRect.contains(x, y)) {
isDraggingHue = true; isDraggingHue = true;
updateHueFromTouch(y); updateHueFromTouch(x);
} else if (satValHitRect.contains(x, y)) { } else if (satValHitRect.contains(x, y)) {
isDraggingSaturation = true; isDraggingSaturation = true;
updateSaturationValueFromTouch(x, y); updateSaturationValueFromTouch(x, y);
} else if (expandedHueRect.contains(x, y)) { } else if (expandedHueRect.contains(x, y)) {
// Handle touch within the expanded hue bar area. // Handle touch within the expanded hue bar area.
isDraggingHue = true; isDraggingHue = true;
updateHueFromTouch(y); updateHueFromTouch(x);
} else if (saturationValueRect.contains(x, y)) { } else if (saturationValueRect.contains(x, y)) {
isDraggingSaturation = true; isDraggingSaturation = true;
updateSaturationValueFromTouch(x, y); updateSaturationValueFromTouch(x, y);
@@ -365,7 +365,7 @@ public class ColorPickerView extends View {
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
// Continue updating values even if touch moves outside the view. // Continue updating values even if touch moves outside the view.
if (isDraggingHue) { if (isDraggingHue) {
updateHueFromTouch(y); updateHueFromTouch(x);
} else if (isDraggingSaturation) { } else if (isDraggingSaturation) {
updateSaturationValueFromTouch(x, y); updateSaturationValueFromTouch(x, y);
} }
@@ -387,12 +387,12 @@ public class ColorPickerView extends View {
/** /**
* Updates the hue value based on touch position, clamping to valid range. * Updates the hue value based on touch position, clamping to valid range.
* *
* @param y The y-coordinate of the touch position. * @param x The x-coordinate of the touch position.
*/ */
private void updateHueFromTouch(float y) { private void updateHueFromTouch(float x) {
// Clamp y to the hue rectangle bounds. // Clamp x to the hue rectangle bounds.
final float clampedY = Utils.clamp(y, hueRect.top, hueRect.bottom); final float clampedX = Utils.clamp(x, hueRect.left, hueRect.right);
final float updatedHue = ((clampedY - hueRect.top) / hueRect.height()) * 360f; final float updatedHue = ((clampedX - hueRect.left) / hueRect.width()) * 360f;
if (hue == updatedHue) { if (hue == updatedHue) {
return; return;
} }

View File

@@ -0,0 +1,173 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.preference.ListPreference;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.*;
import androidx.annotation.NonNull;
import app.revanced.extension.shared.Utils;
/**
* A custom ListPreference that uses a styled custom dialog with a custom checkmark indicator.
*/
@SuppressWarnings({"unused", "deprecation"})
public class CustomDialogListPreference extends ListPreference {
/**
* Custom ArrayAdapter to handle checkmark visibility.
*/
private static class ListPreferenceArrayAdapter extends ArrayAdapter<CharSequence> {
private static class SubViewDataContainer {
ImageView checkIcon;
View placeholder;
TextView itemText;
}
final int layoutResourceId;
final CharSequence[] entryValues;
String selectedValue;
public ListPreferenceArrayAdapter(Context context, int resource, CharSequence[] entries,
CharSequence[] entryValues, String selectedValue) {
super(context, resource, entries);
this.layoutResourceId = resource;
this.entryValues = entryValues;
this.selectedValue = selectedValue;
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent) {
View view = convertView;
SubViewDataContainer holder;
if (view == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(layoutResourceId, parent, false);
holder = new SubViewDataContainer();
holder.checkIcon = view.findViewById(Utils.getResourceIdentifier(
"revanced_check_icon", "id"));
holder.placeholder = view.findViewById(Utils.getResourceIdentifier(
"revanced_check_icon_placeholder", "id"));
holder.itemText = view.findViewById(Utils.getResourceIdentifier(
"revanced_item_text", "id"));
view.setTag(holder);
} else {
holder = (SubViewDataContainer) view.getTag();
}
// Set text.
holder.itemText.setText(getItem(position));
holder.itemText.setTextColor(Utils.getAppForegroundColor());
// Show or hide checkmark and placeholder.
String currentValue = entryValues[position].toString();
boolean isSelected = currentValue.equals(selectedValue);
holder.checkIcon.setVisibility(isSelected ? View.VISIBLE : View.GONE);
holder.checkIcon.setColorFilter(Utils.getAppForegroundColor());
holder.placeholder.setVisibility(isSelected ? View.GONE : View.VISIBLE);
return view;
}
public void setSelectedValue(String value) {
this.selectedValue = value;
}
}
public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomDialogListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomDialogListPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomDialogListPreference(Context context) {
super(context);
}
@Override
protected void showDialog(Bundle state) {
Context context = getContext();
// Create ListView.
ListView listView = new ListView(context);
listView.setId(android.R.id.list);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
// Create custom adapter for the ListView.
ListPreferenceArrayAdapter adapter = new ListPreferenceArrayAdapter(
context,
Utils.getResourceIdentifier("revanced_custom_list_item_checked", "layout"),
getEntries(),
getEntryValues(),
getValue()
);
listView.setAdapter(adapter);
// Set checked item.
String currentValue = getValue();
if (currentValue != null) {
CharSequence[] entryValues = getEntryValues();
for (int i = 0, length = entryValues.length; i < length; i++) {
if (currentValue.equals(entryValues[i].toString())) {
listView.setItemChecked(i, true);
listView.setSelection(i);
break;
}
}
}
// Create the custom dialog without OK button.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : "",
null,
null,
null, // No OK button text.
null, // No OK button action.
() -> {}, // Cancel button action (just dismiss).
null,
null,
true
);
// Add the ListView to the main layout.
LinearLayout mainLayout = dialogPair.second;
LinearLayout.LayoutParams listViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1.0f
);
mainLayout.addView(listView, mainLayout.getChildCount() - 1, listViewParams);
// Handle item click to select value and dismiss dialog.
listView.setOnItemClickListener((parent, view, position, id) -> {
String selectedValue = getEntryValues()[position].toString();
if (callChangeListener(selectedValue)) {
setValue(selectedValue);
adapter.setSelectedValue(selectedValue);
adapter.notifyDataSetChanged();
}
dialogPair.first.dismiss();
});
// Show the dialog.
dialogPair.first.show();
}
}

View File

@@ -1,19 +1,30 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import android.app.AlertDialog; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.preference.Preference; import android.preference.Preference;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import app.revanced.extension.shared.settings.Setting; import android.widget.LinearLayout;
import android.widget.TextView;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import static app.revanced.extension.shared.StringRef.str;
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener { public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
@@ -54,7 +65,8 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
@Override @Override
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
try { try {
// Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened. // Must set text before showing dialog,
// otherwise text is non-selectable if this preference is later reopened.
existingSettings = Setting.exportToJson(getContext()); existingSettings = Setting.exportToJson(getContext());
getEditText().setText(existingSettings); getEditText().setText(existingSettings);
} catch (Exception ex) { } catch (Exception ex) {
@@ -64,18 +76,32 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
} }
@Override @Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { protected void showDialog(Bundle state) {
try { try {
Utils.setEditTextDialogTheme(builder); Context context = getContext();
EditText editText = getEditText();
// Show the user the settings in JSON format. // Create a custom dialog with the EditText.
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> { Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
Utils.setClipboard(getEditText().getText()); context,
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> { str("revanced_pref_import_export_title"), // Title.
importSettings(builder.getContext(), getEditText().getText().toString()); null, // No message (EditText replaces it).
}); editText, // Pass the EditText.
str("revanced_settings_import"), // OK button text.
() -> importSettings(context, editText.getText().toString()), // OK button action.
() -> {}, // Cancel button action (dismiss only).
str("revanced_settings_import_copy"), // Neutral button (Copy) text.
() -> {
// Neutral button (Copy) action. Show the user the settings in JSON format.
Utils.setClipboard(editText.getText());
},
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onPrepareDialogBuilder failure", ex); Logger.printException(() -> "showDialog failure", ex);
} }
} }
@@ -88,7 +114,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings); final boolean rebootNeeded = Setting.importFromJSON(context, replacementSettings);
if (rebootNeeded) { if (rebootNeeded) {
AbstractPreferenceFragment.showRestartDialog(getContext()); AbstractPreferenceFragment.showRestartDialog(context);
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "importSettings failure", ex); Logger.printException(() -> "importSettings failure", ex);
@@ -96,5 +122,4 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
AbstractPreferenceFragment.settingImportInProgress = false; AbstractPreferenceFragment.settingImportInProgress = false;
} }
} }
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.shared.requests.Route.Method.GET; import static app.revanced.extension.shared.requests.Route.Method.GET;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -8,16 +9,19 @@ import android.app.Dialog;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.preference.Preference; import android.preference.Preference;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View;
import android.view.Window; import android.view.Window;
import android.webkit.WebView; import android.webkit.WebView;
import android.webkit.WebViewClient; import android.webkit.WebViewClient;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -48,28 +52,6 @@ public class ReVancedAboutPreference extends Preference {
return text.replace("-", "&#8209;"); // #8209 = non breaking hyphen. return text.replace("-", "&#8209;"); // #8209 = non breaking hyphen.
} }
private static String getColorHexString(int color) {
return String.format("#%06X", (0x00FFFFFF & color));
}
protected boolean isDarkModeEnabled() {
return Utils.isDarkModeEnabled();
}
/**
* Subclasses can override this and provide a themed color.
*/
protected int getLightColor() {
return Color.WHITE;
}
/**
* Subclasses can override this and provide a themed color.
*/
protected int getDarkColor() {
return Color.BLACK;
}
/** /**
* Apps that do not support bundling resources must override this. * Apps that do not support bundling resources must override this.
* *
@@ -86,9 +68,8 @@ public class ReVancedAboutPreference extends Preference {
builder.append("<html>"); builder.append("<html>");
builder.append("<body style=\"text-align: center; padding: 10px;\">"); builder.append("<body style=\"text-align: center; padding: 10px;\">");
final boolean isDarkMode = isDarkModeEnabled(); String foregroundColorHex = Utils.getColorHexString(Utils.getAppForegroundColor());
String backgroundColorHex = getColorHexString(isDarkMode ? getDarkColor() : getLightColor()); String backgroundColorHex = Utils.getColorHexString(Utils.getDialogBackgroundColor());
String foregroundColorHex = getColorHexString(isDarkMode ? getLightColor() : getDarkColor());
// Apply light/dark mode colors. // Apply light/dark mode colors.
builder.append(String.format( builder.append(String.format(
"<style> body { background-color: %s; color: %s; } a { color: %s; } </style>", "<style> body { background-color: %s; color: %s; } a { color: %s; } </style>",
@@ -220,14 +201,38 @@ class WebViewDialog extends Dialog {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
// Create main layout.
LinearLayout mainLayout = new LinearLayout(getContext());
mainLayout.setOrientation(LinearLayout.VERTICAL);
final int padding = dipToPixels(10);
mainLayout.setPadding(padding, padding, padding, padding);
// Set rounded rectangle background.
ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(28), null, null));
mainBackground.getPaint().setColor(Utils.getDialogBackgroundColor());
mainLayout.setBackground(mainBackground);
// Create WebView.
WebView webView = new WebView(getContext()); WebView webView = new WebView(getContext());
webView.setVerticalScrollBarEnabled(false); // Disable the vertical scrollbar.
webView.setOverScrollMode(View.OVER_SCROLL_NEVER);
webView.getSettings().setJavaScriptEnabled(true); webView.getSettings().setJavaScriptEnabled(true);
webView.setWebViewClient(new OpenLinksExternallyWebClient()); webView.setWebViewClient(new OpenLinksExternallyWebClient());
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null); webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null);
setContentView(webView); // Add WebView to layout.
mainLayout.addView(webView);
setContentView(mainLayout);
// Set dialog window attributes
Window window = getWindow();
if (window != null) {
Utils.setDialogWindowParameters(window);
}
} }
private class OpenLinksExternallyWebClient extends WebViewClient { private class OpenLinksExternallyWebClient extends WebViewClient {
@@ -315,7 +320,7 @@ class AboutLinksRoutes {
// Do not show an exception toast if the server is down // Do not show an exception toast if the server is down
final int responseCode = connection.getResponseCode(); final int responseCode = connection.getResponseCode();
if (responseCode != 200) { if (responseCode != 200) {
Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode); Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode);
return NO_CONNECTION_STATIC_LINKS; return NO_CONNECTION_STATIC_LINKS;
} }

View File

@@ -1,14 +1,28 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.shapes.RectShape;
import android.graphics.drawable.shapes.RoundRectShape;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.Paint.Style;
import android.os.Bundle; import android.os.Bundle;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.text.TextUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -44,41 +58,61 @@ public class ResettableEditTextPreference extends EditTextPreference {
this.setting = setting; this.setting = setting;
} }
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
Utils.setEditTextDialogTheme(builder);
if (setting == null) {
String key = getKey();
if (key != null) {
setting = Setting.getSettingFromPath(key);
}
}
if (setting != null) {
builder.setNeutralButton(str("revanced_settings_reset"), null);
}
}
@Override @Override
protected void showDialog(Bundle state) { protected void showDialog(Bundle state) {
super.showDialog(state); try {
Context context = getContext();
EditText editText = getEditText();
// Override the button click listener to prevent dismissing the dialog. // Resolve setting if not already set.
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL); if (setting == null) {
if (button == null) { String key = getKey();
return; if (key != null) {
} setting = Setting.getSettingFromPath(key);
button.setOnClickListener(v -> { }
try {
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
EditText editText = getEditText();
editText.setText(defaultStringValue);
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
} catch (Exception ex) {
Logger.printException(() -> "reset failure", ex);
} }
});
// Set initial EditText value to the current persisted value or empty string.
String initialValue = getText() != null ? getText() : "";
editText.setText(initialValue);
editText.setSelection(initialValue.length()); // Move cursor to end.
// Create custom dialog.
String neutralButtonText = (setting != null) ? str("revanced_settings_reset") : null;
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : "", // Title.
null, // Message is replaced by EditText.
editText, // Pass the EditText.
null, // OK button text.
() -> {
// OK button action. Persist the EditText value when OK is clicked.
String newValue = editText.getText().toString();
if (callChangeListener(newValue)) {
setText(newValue);
}
},
() -> {}, // Cancel button action (dismiss only).
neutralButtonText, // Neutral button text (Reset).
() -> {
// Neutral button action.
if (setting != null) {
try {
String defaultStringValue = Objects.requireNonNull(setting).defaultValue.toString();
editText.setText(defaultStringValue);
editText.setSelection(defaultStringValue.length()); // Move cursor to end of text.
} catch (Exception ex) {
Logger.printException(() -> "reset failure", ex);
}
}
},
false // Do not dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) {
Logger.printException(() -> "showDialog failure", ex);
}
} }
} }

View File

@@ -1,7 +1,6 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
import android.content.Context; import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair; import android.util.Pair;
@@ -24,12 +23,14 @@ import app.revanced.extension.shared.Utils;
* it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}. * it needs to subclass this preference and override {@link #getFirstEntriesToPreserve}.
*/ */
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class SortedListPreference extends ListPreference { public class SortedListPreference extends CustomDialogListPreference {
/** /**
* Sorts the current list entries. * Sorts the current list entries.
* *
* @param firstEntriesToPreserve The number of entries to preserve in their original position. * @param firstEntriesToPreserve The number of entries to preserve in their original position,
* or a negative value to not sort and leave entries
* as they current are.
*/ */
public void sortEntryAndValues(int firstEntriesToPreserve) { public void sortEntryAndValues(int firstEntriesToPreserve) {
CharSequence[] entries = getEntries(); CharSequence[] entries = getEntries();
@@ -44,6 +45,10 @@ public class SortedListPreference extends ListPreference {
throw new IllegalStateException(); throw new IllegalStateException();
} }
if (firstEntriesToPreserve < 0) {
return; // Nothing to do.
}
List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve); List<Pair<CharSequence, CharSequence>> firstEntries = new ArrayList<>(firstEntriesToPreserve);
// Android does not have a triple class like Kotlin, So instead use a nested pair. // Android does not have a triple class like Kotlin, So instead use a nested pair.
@@ -85,10 +90,6 @@ public class SortedListPreference extends ListPreference {
super.setEntryValues(sortedEntryValues); super.setEntryValues(sortedEntryValues);
} }
protected int getFirstEntriesToPreserve() {
return 1;
}
public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { public SortedListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes); super(context, attrs, defStyleAttr, defStyleRes);
@@ -112,4 +113,12 @@ public class SortedListPreference extends ListPreference {
sortEntryAndValues(getFirstEntriesToPreserve()); sortEntryAndValues(getFirstEntriesToPreserve());
} }
/**
* @return The number of first entries to leave exactly where they are, and do not sort them.
* A negative value indicates do not sort any entries.
*/
protected int getFirstEntriesToPreserve() {
return 1;
}
} }

View File

@@ -3,23 +3,29 @@ package app.revanced.extension.spotify.layout.hide.createbutton;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.spotify.shared.ComponentFilters.*;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class HideCreateButtonPatch { public final class HideCreateButtonPatch {
/** /**
* A list of ids of resources which contain the Create button title. * A list of component filters that match whether a navigation bar item is the Create button.
* The main approach used is matching the resource id for the Create button title.
*/ */
private static final List<String> CREATE_BUTTON_TITLE_RES_ID_LIST = List.of( private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
Integer.toString(Utils.getResourceIdentifier("navigationbar_musicappitems_create_title", "string")) new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"),
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded,
// and thus getting the resource identifier for the Create button title always return 0.
// FIXME: Remove this once the above issue is no longer relevant.
new StringComponentFilter("spotify:create-menu")
); );
/** /**
* The old id of the resource which contained the Create button title. Used in older versions of the app. * A component filter for the old id of the resource which contained the Create button title.
* Used in older versions of the app.
*/ */
private static final int OLD_CREATE_BUTTON_TITLE_RES_ID = private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
Utils.getResourceIdentifier("bottom_navigation_bar_create_tab_title", "string"); new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string");
/** /**
* Injection point. This method is called on every navigation bar item to check whether it is the Create button. * Injection point. This method is called on every navigation bar item to check whether it is the Create button.
@@ -31,23 +37,24 @@ public final class HideCreateButtonPatch {
return null; return null;
} }
String stringifiedNavigationBarItem = navigationBarItem.toString(); try {
String stringifiedNavigationBarItem = navigationBarItem.toString();
boolean isCreateButton = false; for (ComponentFilter componentFilter : CREATE_BUTTON_COMPONENT_FILTERS) {
String matchedTitleResId = null; if (componentFilter.filterUnavailable()) {
Logger.printInfo(() -> "returnNullIfIsCreateButton: Filter " +
componentFilter.getFilterRepresentation() + " not available, skipping");
continue;
}
for (String titleResId : CREATE_BUTTON_TITLE_RES_ID_LIST) { if (stringifiedNavigationBarItem.contains(componentFilter.getFilterValue())) {
if (stringifiedNavigationBarItem.contains(titleResId)) { Logger.printInfo(() -> "Hiding Create button because the navigation bar item " +
isCreateButton = true; navigationBarItem + " matched the filter " + componentFilter.getFilterRepresentation());
matchedTitleResId = titleResId; return null;
}
} }
} } catch (Exception ex) {
Logger.printException(() -> "returnNullIfIsCreateButton failure", ex);
if (isCreateButton) {
String finalMatchedTitleResId = matchedTitleResId;
Logger.printInfo(() -> "Hiding Create button because the navigation bar item " + navigationBarItem +
" matched the title resource id " + finalMatchedTitleResId);
return null;
} }
return navigationBarItem; return navigationBarItem;
@@ -58,11 +65,15 @@ public final class HideCreateButtonPatch {
* Create button. * Create button.
*/ */
public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) { public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) {
boolean isCreateButton = oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_TITLE_RES_ID; if (OLD_CREATE_BUTTON_COMPONENT_FILTER.filterUnavailable()) {
Logger.printInfo(() -> "Skipping hiding old Create button because the resource id for " +
OLD_CREATE_BUTTON_COMPONENT_FILTER.resourceName + " is not available");
return false;
}
if (isCreateButton) { if (oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_COMPONENT_FILTER.getResourceId()) {
Logger.printInfo(() -> "Hiding old Create button because the navigation bar item title resource id" + Logger.printInfo(() -> "Hiding old Create button because the navigation bar item title resource id" +
" matched " + OLD_CREATE_BUTTON_TITLE_RES_ID); " matched " + OLD_CREATE_BUTTON_COMPONENT_FILTER.getFilterRepresentation());
return true; return true;
} }

View File

@@ -0,0 +1,85 @@
package app.revanced.extension.spotify.shared;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
public final class ComponentFilters {
public interface ComponentFilter {
@NonNull
String getFilterValue();
String getFilterRepresentation();
default boolean filterUnavailable() {
return false;
}
}
public static final class ResourceIdComponentFilter implements ComponentFilter {
public final String resourceName;
public final String resourceType;
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded.
// 0 is returned when a resource has not been found.
private int resourceId = -1;
@Nullable
private String stringfiedResourceId;
public ResourceIdComponentFilter(String resourceName, String resourceType) {
this.resourceName = resourceName;
this.resourceType = resourceType;
}
public int getResourceId() {
if (resourceId == -1) {
resourceId = Utils.getResourceIdentifier(resourceName, resourceType);
}
return resourceId;
}
@NonNull
@Override
public String getFilterValue() {
if (stringfiedResourceId == null) {
stringfiedResourceId = Integer.toString(getResourceId());
}
return stringfiedResourceId;
}
@Override
public String getFilterRepresentation() {
boolean resourceFound = getResourceId() != 0;
return (resourceFound ? getFilterValue() + " (" : "") + resourceName + (resourceFound ? ")" : "");
}
@Override
public boolean filterUnavailable() {
boolean resourceNotFound = getResourceId() == 0;
if (resourceNotFound) {
Logger.printInfo(() -> "Resource id for " + resourceName + " was not found");
}
return resourceNotFound;
}
}
public static final class StringComponentFilter implements ComponentFilter {
public final String string;
public StringComponentFilter(String string) {
this.string = string;
}
@NonNull
@Override
public String getFilterValue() {
return string;
}
@Override
public String getFilterRepresentation() {
return string;
}
}
}

View File

@@ -16,7 +16,7 @@ public class SpoofSimPatch {
return false; return false;
} }
Logger.initializationException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null); Logger.printException(() -> "Context is not yet set, cannot spoof: " + fieldSpoofed, null);
return true; return true;
} }

View File

@@ -1,179 +0,0 @@
package app.revanced.extension.youtube;
import static app.revanced.extension.shared.Utils.clamp;
import android.app.Activity;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Build;
import android.text.style.ReplacementSpan;
import android.text.TextPaint;
import android.view.Window;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
public class ThemeHelper {
@Nullable
private static Integer darkThemeColor, lightThemeColor;
private static int themeValue;
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static void setTheme(Enum<?> value) {
final int newOrdinalValue = value.ordinal();
if (themeValue != newOrdinalValue) {
themeValue = newOrdinalValue;
Logger.printDebug(() -> "Theme value: " + newOrdinalValue);
}
}
public static boolean isDarkTheme() {
return themeValue == 1;
}
public static void setActivityTheme(Activity activity) {
final var theme = isDarkTheme()
? "Theme.YouTube.Settings.Dark"
: "Theme.YouTube.Settings";
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
}
/**
* Injection point.
*/
@SuppressWarnings("SameReturnValue")
private static String darkThemeResourceName() {
// Value is changed by Theme patch, if included.
return "@color/yt_black3";
}
private static int getThemeColor(String resourceName, int defaultColor) {
try {
return Utils.getColorFromString(resourceName);
} catch (Exception ex) {
// User entered an invalid custom theme color.
// Normally this should never be reached, and no localized strings are needed.
Utils.showToastLong("Invalid custom theme color: " + resourceName);
return defaultColor;
}
}
/**
* @return The dark theme color as specified by the Theme patch (if included),
* or the dark mode background color unpatched YT uses.
*/
public static int getDarkThemeColor() {
if (darkThemeColor == null) {
darkThemeColor = getThemeColor(darkThemeResourceName(), Color.BLACK);
}
return darkThemeColor;
}
/**
* Injection point.
*/
@SuppressWarnings("SameReturnValue")
private static String lightThemeResourceName() {
// Value is changed by Theme patch, if included.
return "@color/yt_white1";
}
/**
* @return The light theme color as specified by the Theme patch (if included),
* or the non dark mode background color unpatched YT uses.
*/
public static int getLightThemeColor() {
if (lightThemeColor == null) {
lightThemeColor = getThemeColor(lightThemeResourceName(), Color.WHITE);
}
return lightThemeColor;
}
public static int getBackgroundColor() {
return isDarkTheme() ? getDarkThemeColor() : getLightThemeColor();
}
public static int getForegroundColor() {
return isDarkTheme() ? getLightThemeColor() : getDarkThemeColor();
}
public static int getDialogBackgroundColor() {
final String colorName = isDarkTheme()
? "yt_black1"
: "yt_white1";
return Utils.getColorFromString(colorName);
}
public static int getToolbarBackgroundColor() {
final String colorName = isDarkTheme()
? "yt_black3"
: "yt_white1";
return Utils.getColorFromString(colorName);
}
/**
* Sets the system navigation bar color for the activity.
* Applies the background color obtained from {@link #getBackgroundColor()} to the navigation bar.
* For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility.
*/
public static void setNavigationBarColor(@Nullable Window window) {
if (window == null) {
Logger.printDebug(() -> "Cannot set navigation bar color, window is null");
return;
}
window.setNavigationBarColor(getBackgroundColor());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
}
/**
* Adjusts the brightness of a color by lightening or darkening it based on the given factor.
* <p>
* If the factor is greater than 1, the color is lightened by interpolating toward white (#FFFFFF).
* If the factor is less than or equal to 1, the color is darkened by scaling its RGB components toward black (#000000).
* The alpha channel remains unchanged.
*
* @param color The input color to adjust, in ARGB format.
* @param factor The adjustment factor. Use values > 1.0f to lighten (e.g., 1.11f for slight lightening)
* or values <= 1.0f to darken (e.g., 0.95f for slight darkening).
* @return The adjusted color in ARGB format.
*/
public static int adjustColorBrightness(int color, float factor) {
final int alpha = Color.alpha(color);
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
if (factor > 1.0f) {
// Lighten: Interpolate toward white (255)
final float t = 1.0f - (1.0f / factor); // Interpolation parameter
red = Math.round(red + (255 - red) * t);
green = Math.round(green + (255 - green) * t);
blue = Math.round(blue + (255 - blue) * t);
} else {
// Darken or no change: Scale toward black
red = (int) (red * factor);
green = (int) (green * factor);
blue = (int) (blue * factor);
}
// Ensure values are within [0, 255]
red = clamp(red, 0, 255);
green = clamp(green, 0, 255);
blue = clamp(blue, 0, 255);
return Color.argb(alpha, red, green, blue);
}
}

View File

@@ -3,7 +3,10 @@ package app.revanced.extension.youtube.patches;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.app.Activity; import android.app.Activity;
import android.app.Dialog;
import android.text.Html; import android.text.Html;
import android.util.Pair;
import android.widget.LinearLayout;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
@@ -63,18 +66,28 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
} }
Utils.runOnMainThread(() -> { Utils.runOnMainThread(() -> {
var alert = new android.app.AlertDialog.Builder(context) try {
.setTitle(str("revanced_check_watch_history_domain_name_dialog_title")) // Create the custom dialog.
.setMessage(Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message"))) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setIconAttribute(android.R.attr.alertDialogIcon) context,
.setPositiveButton(android.R.string.ok, (dialog, which) -> { str("revanced_check_watch_history_domain_name_dialog_title"), // Title.
dialog.dismiss(); Html.fromHtml(str("revanced_check_watch_history_domain_name_dialog_message")), // Message (HTML).
}).setNegativeButton(str("revanced_check_watch_history_domain_name_dialog_ignore"), (dialog, which) -> { null, // No EditText.
Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false); null, // OK button text.
dialog.dismiss(); () -> {}, // OK button action (just dismiss).
}).create(); () -> {}, // Cancel button action (just dismiss).
str("revanced_check_watch_history_domain_name_dialog_ignore"), // Neutral button text.
() -> Settings.CHECK_WATCH_HISTORY_DOMAIN_NAME.save(false), // Neutral button action (Ignore).
true // Dismiss dialog on Neutral button click.
);
Utils.showDialog(context, alert, false, null); // Show the dialog.
Dialog dialog = dialogPair.first;
Utils.showDialog(context, dialog, false, null);
} catch (Exception ex) {
Logger.printException(() -> "checkDnsResolver dialog creation failure", ex);
}
}); });
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "checkDnsResolver failure", ex); Logger.printException(() -> "checkDnsResolver failure", ex);

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.youtube.patches; package app.revanced.extension.youtube.patches;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
@@ -58,6 +59,22 @@ public final class HidePlayerOverlayButtonsPatch {
}); });
} }
/**
* Injection point.
*/
public static void hidePlayerControlButtonsBackground(View rootView) {
try {
if (!Settings.HIDE_PLAYER_CONTROL_BUTTONS_BACKGROUND.get()) {
return;
}
// Each button is an ImageView with a background set to another drawable.
removeImageViewsBackgroundRecursive(rootView);
} catch (Exception ex) {
Logger.printException(() -> "removePlayerControlButtonsBackground failure", ex);
}
}
private static void hideView(View parentView, int resourceId) { private static void hideView(View parentView, int resourceId) {
View nextPreviousButton = parentView.findViewById(resourceId); View nextPreviousButton = parentView.findViewById(resourceId);
@@ -69,4 +86,16 @@ public final class HidePlayerOverlayButtonsPatch {
Logger.printDebug(() -> "Hiding previous/next button"); Logger.printDebug(() -> "Hiding previous/next button");
Utils.hideViewByRemovingFromParentUnderCondition(true, nextPreviousButton); Utils.hideViewByRemovingFromParentUnderCondition(true, nextPreviousButton);
} }
private static void removeImageViewsBackgroundRecursive(View currentView) {
if (currentView instanceof ImageView imageView) {
imageView.setBackground(null);
}
if (currentView instanceof ViewGroup viewGroup) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
removeImageViewsBackgroundRecursive(viewGroup.getChildAt(i));
}
}
}
} }

View File

@@ -7,11 +7,17 @@ public class VersionCheckPatch {
return Utils.getAppVersionName().compareTo(version) >= 0; return Utils.getAppVersionName().compareTo(version) >= 0;
} }
@Deprecated
public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00"); public static final boolean IS_19_17_OR_GREATER = isVersionOrGreater("19.17.00");
@Deprecated
public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00"); public static final boolean IS_19_20_OR_GREATER = isVersionOrGreater("19.20.00");
@Deprecated
public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00"); public static final boolean IS_19_21_OR_GREATER = isVersionOrGreater("19.21.00");
@Deprecated
public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00"); public static final boolean IS_19_26_OR_GREATER = isVersionOrGreater("19.26.00");
@Deprecated
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00"); public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
@Deprecated
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00"); public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00"); public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
} }

View File

@@ -2,13 +2,16 @@ package app.revanced.extension.youtube.patches.announcements;
import static android.text.Html.FROM_HTML_MODE_COMPACT; import static android.text.Html.FROM_HTML_MODE_COMPACT;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS; import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENTS;
import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS; import static app.revanced.extension.youtube.patches.announcements.requests.AnnouncementsRoutes.GET_LATEST_ANNOUNCEMENT_IDS;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.text.Html; import android.text.Html;
import android.text.method.LinkMovementMethod; import android.util.Pair;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import org.json.JSONArray; import org.json.JSONArray;
@@ -120,25 +123,38 @@ public final class AnnouncementsPatch {
final Level finalLevel = level; final Level finalLevel = level;
Utils.runOnMainThread(() -> { Utils.runOnMainThread(() -> {
// Show the announcement. // Create the custom dialog and show the announcement.
var alert = new AlertDialog.Builder(context) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(finalTitle) context,
.setMessage(finalMessage) finalTitle, // Title.
.setIcon(finalLevel.icon) finalMessage, // Message.
.setPositiveButton(android.R.string.ok, (dialog, which) -> { null, // No EditText.
Settings.ANNOUNCEMENT_LAST_ID.save(finalId); null, // OK button text.
dialog.dismiss(); () -> Settings.ANNOUNCEMENT_LAST_ID.save(finalId), // OK button action.
}).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> { () -> {}, // Cancel button action (dismiss only).
dialog.dismiss(); str("revanced_announcements_dialog_dismiss"), // Neutral button text.
}) () -> {}, // Neutral button action (dismiss only).
.setCancelable(false) true // Dismiss dialog when onNeutralClick.
.create(); );
Utils.showDialog(context, alert, false, (AlertDialog dialog) -> { Dialog dialog = dialogPair.first;
// Make links clickable. LinearLayout mainLayout = dialogPair.second;
((TextView) dialog.findViewById(android.R.id.message))
.setMovementMethod(LinkMovementMethod.getInstance()); // Set the icon for the title TextView
}); for (int i = 0, childCould = mainLayout.getChildCount(); i < childCould; i++) {
View child = mainLayout.getChildAt(i);
if (child instanceof TextView childTextView && finalTitle.equals(childTextView.getText().toString())) {
childTextView.setCompoundDrawablesWithIntrinsicBounds(
finalLevel.icon, 0, 0, 0);
childTextView.setCompoundDrawablePadding(dipToPixels(8));
}
}
// Set dialog as non-cancelable.
dialog.setCancelable(false);
// Show the dialog.
Utils.showDialog(context, dialog);
}); });
} catch (Exception e) { } catch (Exception e) {
final var message = "Failed to get announcement"; final var message = "Failed to get announcement";

View File

@@ -121,12 +121,14 @@ public final class AdsFilter extends Filter {
playerShoppingShelf = new StringFilterGroup( playerShoppingShelf = new StringFilterGroup(
Settings.HIDE_PLAYER_STORE_SHELF, Settings.HIDE_PLAYER_STORE_SHELF,
"expandable_list.eml",
"horizontal_shelf.eml" "horizontal_shelf.eml"
); );
playerShoppingShelfBuffer = new ByteArrayFilterGroup( playerShoppingShelfBuffer = new ByteArrayFilterGroup(
null, null,
"shopping_item_card_list.eml" "shopping_link_item",
"shopping_item_card_list"
); );
channelProfile = new StringFilterGroup( channelProfile = new StringFilterGroup(

View File

@@ -6,7 +6,7 @@ import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQual
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** /**
* Abuse LithoFilter for {@link AdvancedVideoQualityMenuPatch}. * LithoFilter for {@link AdvancedVideoQualityMenuPatch}.
*/ */
public final class AdvancedVideoQualityMenuFilter extends Filter { public final class AdvancedVideoQualityMenuFilter extends Filter {
// Must be volatile or synchronized, as litho filtering runs off main thread // Must be volatile or synchronized, as litho filtering runs off main thread

View File

@@ -267,6 +267,10 @@ public final class ShortsFilter extends Filter {
Settings.HIDE_SHORTS_GREEN_SCREEN_BUTTON, Settings.HIDE_SHORTS_GREEN_SCREEN_BUTTON,
"greenscreen_temp" "greenscreen_temp"
), ),
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_NEW_POSTS_BUTTON,
"yt_outline_box_pencil"
),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_HASHTAG_BUTTON, Settings.HIDE_SHORTS_HASHTAG_BUTTON,
"yt_outline_hashtag_" "yt_outline_hashtag_"

View File

@@ -13,8 +13,6 @@ import app.revanced.extension.youtube.settings.Settings;
/** /**
* This patch contains the logic to always open the advanced video quality menu. * This patch contains the logic to always open the advanced video quality menu.
* Two methods are required, because the quality menu is a RecyclerView in the new YouTube version
* and a ListView in the old one.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class AdvancedVideoQualityMenuPatch { public final class AdvancedVideoQualityMenuPatch {
@@ -76,7 +74,7 @@ public final class AdvancedVideoQualityMenuPatch {
/** /**
* Injection point. * Injection point.
* *
* Used if spoofing to an old app version, and also used for the Shorts video quality flyout. * Shorts video quality flyout.
*/ */
public static void showAdvancedVideoQualityMenu(ListView listView) { public static void showAdvancedVideoQualityMenu(ListView listView) {
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return; if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
@@ -90,14 +88,12 @@ public final class AdvancedVideoQualityMenuPatch {
final var indexOfAdvancedQualityMenuItem = 4; final var indexOfAdvancedQualityMenuItem = 4;
if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return; if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return;
Logger.printDebug(() -> "Found advanced menu item in old type of quality menu");
listView.setSoundEffectsEnabled(false); listView.setSoundEffectsEnabled(false);
final var qualityItemMenuPosition = 4; final var qualityItemMenuPosition = 4;
listView.performItemClick(null, qualityItemMenuPosition, 0); listView.performItemClick(null, qualityItemMenuPosition, 0);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "showOldVideoQualityMenu failure", ex); Logger.printException(() -> "showAdvancedVideoQualityMenu failure", ex);
} }
} }

View File

@@ -19,13 +19,15 @@ import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.RoundRectShape;
import android.icu.text.NumberFormat; import android.icu.text.NumberFormat;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.animation.Animation;
import android.view.Gravity; import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent; import android.view.ViewParent;
import android.view.Window; import android.view.Window;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.Button; import android.widget.Button;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.GridLayout; import android.widget.GridLayout;
@@ -39,7 +41,6 @@ import java.util.function.Function;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch; import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@@ -240,6 +241,9 @@ public class CustomPlaybackSpeedPatch {
// Store the dialog reference. // Store the dialog reference.
currentDialog = new WeakReference<>(dialog); currentDialog = new WeakReference<>(dialog);
// Enable dismissing the dialog when tapping outside.
dialog.setCanceledOnTouchOutside(true);
// Create main vertical LinearLayout for dialog content. // Create main vertical LinearLayout for dialog content.
LinearLayout mainLayout = new LinearLayout(context); LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL); mainLayout.setOrientation(LinearLayout.VERTICAL);
@@ -259,15 +263,15 @@ public class CustomPlaybackSpeedPatch {
// Set rounded rectangle background for the main layout. // Set rounded rectangle background for the main layout.
RoundRectShape roundRectShape = new RoundRectShape( RoundRectShape roundRectShape = new RoundRectShape(
createCornerRadii(12), null, null); Utils.createCornerRadii(12), null, null);
ShapeDrawable background = new ShapeDrawable(roundRectShape); ShapeDrawable background = new ShapeDrawable(roundRectShape);
background.getPaint().setColor(ThemeHelper.getDialogBackgroundColor()); background.getPaint().setColor(Utils.getDialogBackgroundColor());
mainLayout.setBackground(background); mainLayout.setBackground(background);
// Add handle bar at the top. // Add handle bar at the top.
View handleBar = new View(context); View handleBar = new View(context);
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape( ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(4), null, null)); Utils.createCornerRadii(4), null, null));
handleBackground.getPaint().setColor(getAdjustedBackgroundColor(true)); handleBackground.getPaint().setColor(getAdjustedBackgroundColor(true));
handleBar.setBackground(handleBackground); handleBar.setBackground(handleBackground);
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(
@@ -285,7 +289,7 @@ public class CustomPlaybackSpeedPatch {
float currentSpeed = VideoInformation.getPlaybackSpeed(); float currentSpeed = VideoInformation.getPlaybackSpeed();
// Initially show with only 0 minimum digits, so 1.0 shows as 1x // Initially show with only 0 minimum digits, so 1.0 shows as 1x
currentSpeedText.setText(formatSpeedStringX(currentSpeed, 0)); currentSpeedText.setText(formatSpeedStringX(currentSpeed, 0));
currentSpeedText.setTextColor(ThemeHelper.getForegroundColor()); currentSpeedText.setTextColor(Utils.getAppForegroundColor());
currentSpeedText.setTextSize(16); currentSpeedText.setTextSize(16);
currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD); currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD);
currentSpeedText.setGravity(Gravity.CENTER); currentSpeedText.setGravity(Gravity.CENTER);
@@ -305,7 +309,8 @@ public class CustomPlaybackSpeedPatch {
// Create minus button. // Create minus button.
Button minusButton = new Button(context, null, 0); // Disable default theme style. Button minusButton = new Button(context, null, 0); // Disable default theme style.
minusButton.setText(""); // No text on button. minusButton.setText(""); // No text on button.
ShapeDrawable minusBackground = new ShapeDrawable(new RoundRectShape(createCornerRadii(20), null, null)); ShapeDrawable minusBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(20), null, null));
minusBackground.getPaint().setColor(getAdjustedBackgroundColor(false)); minusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
minusButton.setBackground(minusBackground); minusButton.setBackground(minusBackground);
OutlineSymbolDrawable minusDrawable = new OutlineSymbolDrawable(false); // Minus symbol. OutlineSymbolDrawable minusDrawable = new OutlineSymbolDrawable(false); // Minus symbol.
@@ -319,9 +324,9 @@ public class CustomPlaybackSpeedPatch {
speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax)); speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax));
speedSlider.setProgress(speedToProgressValue(currentSpeed)); speedSlider.setProgress(speedToProgressValue(currentSpeed));
speedSlider.getProgressDrawable().setColorFilter( speedSlider.getProgressDrawable().setColorFilter(
ThemeHelper.getForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar. Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar.
speedSlider.getThumb().setColorFilter( speedSlider.getThumb().setColorFilter(
ThemeHelper.getForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb. Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb.
LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f); 0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
sliderParams.setMargins(dip5, 0, dip5, 0); // 5dp to -/+ buttons. sliderParams.setMargins(dip5, 0, dip5, 0); // 5dp to -/+ buttons.
@@ -331,7 +336,7 @@ public class CustomPlaybackSpeedPatch {
Button plusButton = new Button(context, null, 0); // Disable default theme style. Button plusButton = new Button(context, null, 0); // Disable default theme style.
plusButton.setText(""); // No text on button. plusButton.setText(""); // No text on button.
ShapeDrawable plusBackground = new ShapeDrawable(new RoundRectShape( ShapeDrawable plusBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(20), null, null)); Utils.createCornerRadii(20), null, null));
plusBackground.getPaint().setColor(getAdjustedBackgroundColor(false)); plusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
plusButton.setBackground(plusBackground); plusButton.setBackground(plusBackground);
OutlineSymbolDrawable plusDrawable = new OutlineSymbolDrawable(true); // Plus symbol. OutlineSymbolDrawable plusDrawable = new OutlineSymbolDrawable(true); // Plus symbol.
@@ -418,13 +423,13 @@ public class CustomPlaybackSpeedPatch {
// Create speed button. // Create speed button.
Button speedButton = new Button(context, null, 0); Button speedButton = new Button(context, null, 0);
speedButton.setText(speedFormatter.format(speed)); // Do not use 'x' speed format. speedButton.setText(speedFormatter.format(speed)); // Do not use 'x' speed format.
speedButton.setTextColor(ThemeHelper.getForegroundColor()); speedButton.setTextColor(Utils.getAppForegroundColor());
speedButton.setTextSize(12); speedButton.setTextSize(12);
speedButton.setAllCaps(false); speedButton.setAllCaps(false);
speedButton.setGravity(Gravity.CENTER); speedButton.setGravity(Gravity.CENTER);
ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape( ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(20), null, null)); Utils.createCornerRadii(20), null, null));
buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false)); buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
speedButton.setBackground(buttonBackground); speedButton.setBackground(buttonBackground);
speedButton.setPadding(dip5, dip5, dip5, dip5); speedButton.setPadding(dip5, dip5, dip5, dip5);
@@ -442,7 +447,7 @@ public class CustomPlaybackSpeedPatch {
TextView normalLabel = new TextView(context); TextView normalLabel = new TextView(context);
// Use same 'Normal' string as stock YouTube. // Use same 'Normal' string as stock YouTube.
normalLabel.setText(str("normal_playback_rate_label")); normalLabel.setText(str("normal_playback_rate_label"));
normalLabel.setTextColor(ThemeHelper.getForegroundColor()); normalLabel.setTextColor(Utils.getAppForegroundColor());
normalLabel.setTextSize(10); normalLabel.setTextSize(10);
normalLabel.setGravity(Gravity.CENTER); normalLabel.setGravity(Gravity.CENTER);
@@ -489,6 +494,77 @@ public class CustomPlaybackSpeedPatch {
window.setBackgroundDrawable(null); // Remove default dialog background. window.setBackgroundDrawable(null); // Remove default dialog background.
} }
// Apply slide-in animation when showing the dialog.
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
slideInABottomAnimation.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideInABottomAnimation);
// Set touch listener on mainLayout to enable drag-to-dismiss.
//noinspection ClickableViewAccessibility
mainLayout.setOnTouchListener(new View.OnTouchListener() {
/** Threshold for dismissing the dialog. */
final float dismissThreshold = dipToPixels(100); // Distance to drag to dismiss.
/** Store initial Y position of touch. */
float touchY;
/** Track current translation. */
float translationY;
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// Capture initial Y position of touch.
touchY = event.getRawY();
translationY = mainLayout.getTranslationY();
return true;
case MotionEvent.ACTION_MOVE:
// Calculate drag distance and apply translation downwards only.
final float deltaY = event.getRawY() - touchY;
// Only allow downward drag (positive deltaY).
if (deltaY >= 0) {
mainLayout.setTranslationY(translationY + deltaY);
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
// Check if dialog should be dismissed based on drag distance.
if (mainLayout.getTranslationY() > dismissThreshold) {
// Animate dialog off-screen and dismiss.
//noinspection ExtractMethodRecommender
final float remainingDistance = context.getResources().getDisplayMetrics().heightPixels
- mainLayout.getTop();
TranslateAnimation slideOut = new TranslateAnimation(
0, 0, mainLayout.getTranslationY(), remainingDistance);
slideOut.setDuration(fadeDurationFast);
slideOut.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {}
@Override
public void onAnimationEnd(Animation animation) {
dialog.dismiss();
}
@Override
public void onAnimationRepeat(Animation animation) {}
});
mainLayout.startAnimation(slideOut);
} else {
// Animate back to original position if not dragged far enough.
TranslateAnimation slideBack = new TranslateAnimation(
0, 0, mainLayout.getTranslationY(), 0);
slideBack.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideBack);
mainLayout.setTranslationY(0);
}
return true;
default:
return false;
}
}
});
// Create observer for PlayerType changes. // Create observer for PlayerType changes.
Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() { Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() {
@Override @Override
@@ -515,27 +591,9 @@ public class CustomPlaybackSpeedPatch {
Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss"); Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss");
}); });
// Apply slide-in animation when showing the dialog.
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
slideInABottomAnimation.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideInABottomAnimation);
dialog.show(); // Display the dialog. dialog.show(); // Display the dialog.
} }
/**
* Creates an array of corner radii for a rounded rectangle shape.
*
* @param dp The radius in density-independent pixels (dp) to apply to all corners.
* @return An array of eight float values representing the corner radii
* (top-left, top-right, bottom-right, bottom-left).
*/
private static float[] createCornerRadii(float dp) {
final float radius = dipToPixels(dp);
return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
}
/** /**
* @param speed The playback speed value to format. * @param speed The playback speed value to format.
* @return A string representation of the speed with 'x' (e.g. "1.25x" or "1.00x"). * @return A string representation of the speed with 'x' (e.g. "1.25x" or "1.00x").
@@ -573,12 +631,12 @@ public class CustomPlaybackSpeedPatch {
* for light themes to ensure visual contrast. * for light themes to ensure visual contrast.
*/ */
public static int getAdjustedBackgroundColor(boolean isHandleBar) { public static int getAdjustedBackgroundColor(boolean isHandleBar) {
final int baseColor = ThemeHelper.getDialogBackgroundColor(); final int baseColor = Utils.getDialogBackgroundColor();
float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme. float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme.
float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme. float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme.
return ThemeHelper.isDarkTheme() return Utils.isDarkModeEnabled()
? ThemeHelper.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme. ? Utils.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme. : Utils.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme.
} }
} }
@@ -592,7 +650,7 @@ class OutlineSymbolDrawable extends Drawable {
OutlineSymbolDrawable(boolean isPlus) { OutlineSymbolDrawable(boolean isPlus) {
this.isPlus = isPlus; this.isPlus = isPlus;
paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Enable anti-aliasing for smooth rendering. paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Enable anti-aliasing for smooth rendering.
paint.setColor(ThemeHelper.getForegroundColor()); paint.setColor(Utils.getAppForegroundColor());
paint.setStyle(Paint.Style.STROKE); // Use stroke style for outline. paint.setStyle(Paint.Style.STROKE); // Use stroke style for outline.
paint.setStrokeWidth(dipToPixels(1)); // 1dp stroke width. paint.setStrokeWidth(dipToPixels(1)); // 1dp stroke width.
} }

View File

@@ -2,6 +2,7 @@ package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.clamp; import static app.revanced.extension.shared.Utils.clamp;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Color; import android.graphics.Color;
@@ -173,23 +174,15 @@ public final class SeekbarColorPatch {
*/ */
public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) { public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) {
try { try {
if (!SEEKBAR_CUSTOM_COLOR_ENABLED) { SplashScreenAnimationStyle animationStyle = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
if (!SEEKBAR_CUSTOM_COLOR_ENABLED
// Black and white animations cannot use color replacements.
|| animationStyle == SplashScreenAnimationStyle.FPS_30_BLACK_AND_WHITE
|| animationStyle == SplashScreenAnimationStyle.FPS_60_BLACK_AND_WHITE) {
view.patch_setAnimation(resourceId); view.patch_setAnimation(resourceId);
return; return;
} }
//noinspection ConstantConditions
if (false) { // Set true to force slow animation for development.
final int longAnimation = Utils.getResourceIdentifier(
Utils.isDarkModeEnabled()
? "startup_animation_5s_30fps_dark"
: "startup_animation_5s_30fps_light",
"raw");
if (longAnimation != 0) {
resourceId = longAnimation;
}
}
// Must specify primary key name otherwise the morphing YT logo color is also changed. // Must specify primary key name otherwise the morphing YT logo color is also changed.
String originalKey = "\"k\":"; String originalKey = "\"k\":";
String originalPrimary = originalKey + "[1,0,0.2,1]"; String originalPrimary = originalKey + "[1,0,0.2,1]";
@@ -199,21 +192,16 @@ public final class SeekbarColorPatch {
String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]); String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]);
String json = loadRawResourceAsString(resourceId); String json = loadRawResourceAsString(resourceId);
if (json == null) { String replacement = json
return; // Should never happen. .replace(originalPrimary, replacementPrimary)
} .replace(originalAccent, replacementAccent);
if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) { if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) {
String jsonFinal = json; Logger.printException(() -> "Could not replace splash animation colors: " + json);
Logger.printException(() -> "Could not replace launch animation colors: " + jsonFinal);
} }
Logger.printDebug(() -> "Replacing Lottie animation JSON");
json = json.replace(originalPrimary, replacementPrimary);
json = json.replace(originalAccent, replacementAccent);
// cacheKey is not needed since the animation will not be reused. // cacheKey is not needed since the animation will not be reused.
view.patch_setAnimation(new ByteArrayInputStream(json.getBytes()), null); view.patch_setAnimation(new ByteArrayInputStream(replacement.getBytes()), null);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "setSplashAnimationLottie failure", ex); Logger.printException(() -> "setSplashAnimationLottie failure", ex);
} }
@@ -234,8 +222,7 @@ public final class SeekbarColorPatch {
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) { Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
return scanner.next(); return scanner.next();
} catch (IOException e) { } catch (IOException e) {
Logger.printException(() -> "Could not load resource: " + resourceId); throw new IllegalStateException("Could not load resource: " + resourceId);
return null;
} }
} }

View File

@@ -1,11 +1,48 @@
package app.revanced.extension.youtube.patches.theme; package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle.styleFromOrdinal;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class ThemePatch { public class ThemePatch {
public enum SplashScreenAnimationStyle {
DEFAULT(0),
FPS_60_ONE_SECOND(1),
FPS_60_TWO_SECOND(2),
FPS_60_FIVE_SECOND(3),
FPS_60_BLACK_AND_WHITE(4),
FPS_30_ONE_SECOND(5),
FPS_30_TWO_SECOND(6),
FPS_30_FIVE_SECOND(7),
FPS_30_BLACK_AND_WHITE(8);
// There exists a 10th json style used as the switch statement default,
// but visually it is identical to 60fps one second.
@Nullable
static SplashScreenAnimationStyle styleFromOrdinal(int style) {
// Alternatively can return using values()[style]
for (SplashScreenAnimationStyle value : values()) {
if (value.style == style) {
return value;
}
}
return null;
}
final int style;
SplashScreenAnimationStyle(int style) {
this.style = style;
}
}
// color constants used in relation with litho components // color constants used in relation with litho components
private static final int[] WHITE_VALUES = { private static final int[] WHITE_VALUES = {
-1, // comments chip background -1, // comments chip background
@@ -37,7 +74,7 @@ public class ThemePatch {
* @return The new or original color value * @return The new or original color value
*/ */
public static int getValue(int originalValue) { public static int getValue(int originalValue) {
if (ThemeHelper.isDarkTheme()) { if (Utils.isDarkModeEnabled()) {
if (anyEquals(originalValue, DARK_VALUES)) return BLACK_COLOR; if (anyEquals(originalValue, DARK_VALUES)) return BLACK_COLOR;
} else { } else {
if (anyEquals(originalValue, WHITE_VALUES)) return WHITE_COLOR; if (anyEquals(originalValue, WHITE_VALUES)) return WHITE_COLOR;
@@ -58,4 +95,22 @@ public class ThemePatch {
public static boolean gradientLoadingScreenEnabled(boolean original) { public static boolean gradientLoadingScreenEnabled(boolean original) {
return GRADIENT_LOADING_SCREEN_ENABLED; return GRADIENT_LOADING_SCREEN_ENABLED;
} }
/**
* Injection point.
*/
public static int getLoadingScreenType(int original) {
SplashScreenAnimationStyle style = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
if (style == SplashScreenAnimationStyle.DEFAULT) {
return original;
}
final int replacement = style.style;
if (original != replacement) {
Logger.printDebug(() -> "Overriding splash screen style from: "
+ styleFromOrdinal(original) + " to: " + style);
}
return replacement;
}
} }

View File

@@ -30,11 +30,15 @@ import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.*; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData; import app.revanced.extension.youtube.returnyoutubedislike.requests.RYDVoteData;
import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi; import app.revanced.extension.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@@ -177,7 +181,7 @@ public class ReturnYouTubeDislike {
* Ideally, this would be the actual color YT uses at runtime. * Ideally, this would be the actual color YT uses at runtime.
*/ */
private static int getSeparatorColor() { private static int getSeparatorColor() {
return ThemeHelper.isDarkTheme() return Utils.isDarkModeEnabled()
? 0x33FFFFFF ? 0x33FFFFFF
: 0xFFD9D9D9; : 0xFFD9D9D9;
} }

View File

@@ -14,7 +14,6 @@ import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage; import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.VersionCheckPatch; import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch; import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
@@ -27,6 +26,8 @@ import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFrag
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class LicenseActivityHook { public class LicenseActivityHook {
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
private static ViewGroup.LayoutParams toolbarLayoutParams; private static ViewGroup.LayoutParams toolbarLayoutParams;
public static void setToolbarLayoutParams(Toolbar toolbar) { public static void setToolbarLayoutParams(Toolbar toolbar) {
@@ -78,8 +79,8 @@ public class LicenseActivityHook {
*/ */
public static void initialize(Activity licenseActivity) { public static void initialize(Activity licenseActivity) {
try { try {
ThemeHelper.setActivityTheme(licenseActivity); setActivityTheme(licenseActivity);
ThemeHelper.setNavigationBarColor(licenseActivity.getWindow()); ReVancedPreferenceFragment.setNavigationBarColor(licenseActivity.getWindow());
licenseActivity.setContentView(getResourceIdentifier( licenseActivity.setContentView(getResourceIdentifier(
"revanced_settings_with_toolbar", "layout")); "revanced_settings_with_toolbar", "layout"));
@@ -114,7 +115,7 @@ public class LicenseActivityHook {
toolBarParent.removeView(dummyToolbar); toolBarParent.removeView(dummyToolbar);
Toolbar toolbar = new Toolbar(toolBarParent.getContext()); Toolbar toolbar = new Toolbar(toolBarParent.getContext());
toolbar.setBackgroundColor(ThemeHelper.getToolbarBackgroundColor()); toolbar.setBackgroundColor(getToolbarBackgroundColor());
toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable()); toolbar.setNavigationIcon(ReVancedPreferenceFragment.getBackButtonDrawable());
toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string")); toolbar.setTitle(getResourceIdentifier("revanced_settings_title", "string"));
@@ -124,7 +125,7 @@ public class LicenseActivityHook {
TextView toolbarTextView = Utils.getChildView(toolbar, false, TextView toolbarTextView = Utils.getChildView(toolbar, false,
view -> view instanceof TextView); view -> view instanceof TextView);
if (toolbarTextView != null) { if (toolbarTextView != null) {
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor()); toolbarTextView.setTextColor(Utils.getAppForegroundColor());
} }
setToolbarLayoutParams(toolbar); setToolbarLayoutParams(toolbar);
@@ -135,4 +136,34 @@ public class LicenseActivityHook {
toolBarParent.addView(toolbar, 0); toolBarParent.addView(toolbar, 0);
} }
public static void setActivityTheme(Activity activity) {
final var theme = Utils.isDarkModeEnabled()
? "Theme.YouTube.Settings.Dark"
: "Theme.YouTube.Settings";
activity.setTheme(getResourceIdentifier(theme, "style"));
}
public static int getToolbarBackgroundColor() {
final String colorName = Utils.isDarkModeEnabled()
? "yt_black3"
: "yt_white1";
return Utils.getColorFromString(colorName);
}
/**
* Injection point.
*
* Updates dark/light mode since YT settings can force light/dark mode
* which can differ from the global device settings.
*/
@SuppressWarnings("unused")
public static void updateLightDarkModeStatus(Enum<?> value) {
final int themeOrdinal = value.ordinal();
if (currentThemeValueOrdinal != themeOrdinal) {
currentThemeValueOrdinal = themeOrdinal;
Utils.setIsDarkModeEnabled(themeOrdinal == 1);
}
}
} }

View File

@@ -4,9 +4,10 @@ import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier; import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
import android.util.Pair;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
@@ -18,6 +19,7 @@ import android.widget.SearchView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.ArrayList; import java.util.ArrayList;
@@ -31,7 +33,6 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage; import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.StringSetting; import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment; import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
/** /**
@@ -58,11 +59,7 @@ public class SearchViewController {
GradientDrawable background = new GradientDrawable(); GradientDrawable background = new GradientDrawable();
background.setShape(GradientDrawable.RECTANGLE); background.setShape(GradientDrawable.RECTANGLE);
background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius. background.setCornerRadius(28 * context.getResources().getDisplayMetrics().density); // 28dp corner radius.
int baseColor = ThemeHelper.getBackgroundColor(); background.setColor(getSearchViewBackground());
int adjustedColor = ThemeHelper.isDarkTheme()
? ThemeHelper.adjustColorBrightness(baseColor, 1.11f) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
background.setColor(adjustedColor);
return background; return background;
} }
@@ -72,10 +69,17 @@ public class SearchViewController {
private static GradientDrawable createSuggestionBackgroundDrawable(Context context) { private static GradientDrawable createSuggestionBackgroundDrawable(Context context) {
GradientDrawable background = new GradientDrawable(); GradientDrawable background = new GradientDrawable();
background.setShape(GradientDrawable.RECTANGLE); background.setShape(GradientDrawable.RECTANGLE);
background.setCornerRadius(8 * context.getResources().getDisplayMetrics().density); // 8dp corner radius. background.setColor(getSearchViewBackground());
return background; return background;
} }
@ColorInt
public static int getSearchViewBackground() {
return Utils.isDarkModeEnabled()
? Utils.adjustColorBrightness(Utils.getDialogBackgroundColor(), 1.11f)
: Utils.adjustColorBrightness(Utils.getThemeLightColor(), 0.95f);
}
/** /**
* Adds search view components to the activity. * Adds search view components to the activity.
*/ */
@@ -171,10 +175,6 @@ public class SearchViewController {
final int actionSearchId = getResourceIdentifier("action_search", "id"); final int actionSearchId = getResourceIdentifier("action_search", "id");
toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu")); toolbar.inflateMenu(getResourceIdentifier("revanced_search_menu", "menu"));
MenuItem searchItem = toolbar.getMenu().findItem(actionSearchId); MenuItem searchItem = toolbar.getMenu().findItem(actionSearchId);
searchItem.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_search_white_24"
: "yt_outline_search_black_24",
"drawable")).setTooltipText(null);
// Set menu item click listener. // Set menu item click listener.
toolbar.setOnMenuItemClickListener(item -> { toolbar.setOnMenuItemClickListener(item -> {
@@ -316,12 +316,7 @@ public class SearchViewController {
private void closeSearch() { private void closeSearch() {
isSearchActive = false; isSearchActive = false;
toolbar.getMenu().findItem(getResourceIdentifier( toolbar.getMenu().findItem(getResourceIdentifier(
"action_search", "id")) "action_search", "id")).setVisible(true);
.setIcon(getResourceIdentifier(ThemeHelper.isDarkTheme()
? "yt_outline_search_white_24"
: "yt_outline_search_black_24",
"drawable")
).setVisible(true);
toolbar.setTitle(originalTitle); toolbar.setTitle(originalTitle);
searchContainer.setVisibility(View.GONE); searchContainer.setVisibility(View.GONE);
searchView.setQuery("", false); searchView.setQuery("", false);
@@ -365,13 +360,22 @@ public class SearchViewController {
// Set long click listener for deletion confirmation. // Set long click listener for deletion confirmation.
convertView.setOnLongClickListener(v -> { convertView.setOnLongClickListener(v -> {
new AlertDialog.Builder(activity) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(query) activity,
.setMessage(str("revanced_settings_search_remove_message")) query, // Title.
.setPositiveButton(android.R.string.ok, str("revanced_settings_search_remove_message"), // Message.
(dialog, which) -> removeSearchQuery(query)) null, // No EditText.
.setNegativeButton(android.R.string.cancel, null) null, // OK button text.
.show(); () -> removeSearchQuery(query), // OK button action.
() -> {}, // Cancel button action (dismiss only).
null, // No Neutral button text.
() -> {}, // Neutral button action (dismiss only).
true // Dismiss dialog when onNeutralClick.
);
Dialog dialog = dialogPair.first;
dialog.setCancelable(true); // Allow dismissal via back button.
dialog.show(); // Show the dialog.
return true; return true;
}); });

View File

@@ -21,14 +21,12 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerT
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType; import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability; import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability; import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
import android.graphics.Color; import android.graphics.Color;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
@@ -40,12 +38,14 @@ import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.shared.settings.LongSetting; import app.revanced.extension.shared.settings.LongSetting;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting; import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
import app.revanced.extension.youtube.patches.MiniplayerPatch; import app.revanced.extension.youtube.patches.MiniplayerPatch;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
public class Settings extends BaseSettings { public class Settings extends BaseSettings {
// Video // Video
@@ -135,6 +135,7 @@ public class Settings extends BaseSettings {
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);
public static final BooleanSetting HIDE_PLAYER_CONTROL_BUTTONS_BACKGROUND = new BooleanSetting("revanced_hide_player_control_buttons_background", FALSE, true);
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE); public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
public static final BooleanSetting HIDE_CHANNEL_MEMBER_SHELF = new BooleanSetting("revanced_hide_channel_member_shelf", TRUE); public static final BooleanSetting HIDE_CHANNEL_MEMBER_SHELF = new BooleanSetting("revanced_hide_channel_member_shelf", TRUE);
public static final BooleanSetting HIDE_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_community_guidelines", TRUE); public static final BooleanSetting HIDE_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_community_guidelines", TRUE);
@@ -226,6 +227,8 @@ public class Settings extends BaseSettings {
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message"); public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true); public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true); public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, 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");
@@ -260,6 +263,7 @@ public class Settings extends BaseSettings {
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_NEW_POSTS_BUTTON = new BooleanSetting("revanced_hide_shorts_new_posts_button", TRUE);
public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE); public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE);
public static final BooleanSetting HIDE_SHORTS_HISTORY = new BooleanSetting("revanced_hide_shorts_history", FALSE); public static final BooleanSetting HIDE_SHORTS_HISTORY = new BooleanSetting("revanced_hide_shorts_history", FALSE);
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE); public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);

View File

@@ -3,17 +3,18 @@ package app.revanced.extension.youtube.settings.preference;
import static app.revanced.extension.shared.StringRef.sf; import static app.revanced.extension.shared.StringRef.sf;
import android.content.Context; import android.content.Context;
import android.preference.ListPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch; import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
/** /**
* A custom ListPreference that uses a styled custom dialog with a custom checkmark indicator.
* Custom video speeds used by {@link CustomPlaybackSpeedPatch}. * Custom video speeds used by {@link CustomPlaybackSpeedPatch}.
*/ */
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public final class CustomVideoSpeedListPreference extends ListPreference { public final class CustomVideoSpeedListPreference extends CustomDialogListPreference {
/** /**
* Initialize a settings preference list with the available playback speeds. * Initialize a settings preference list with the available playback speeds.
@@ -59,4 +60,5 @@ public final class CustomVideoSpeedListPreference extends ListPreference {
public CustomVideoSpeedListPreference(Context context) { public CustomVideoSpeedListPreference(Context context) {
super(context); super(context);
} }
} }

View File

@@ -18,6 +18,7 @@ import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.style.BackgroundColorSpan; import android.text.style.BackgroundColorSpan;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
@@ -40,7 +41,6 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment; import app.revanced.extension.shared.settings.preference.AbstractPreferenceFragment;
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory; import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.settings.LicenseActivityHook; import app.revanced.extension.youtube.settings.LicenseActivityHook;
import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup; import app.revanced.extension.youtube.sponsorblock.ui.SponsorBlockPreferenceGroup;
@@ -71,11 +71,27 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() { public static Drawable getBackButtonDrawable() {
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme() final int backButtonResource = getResourceIdentifier("revanced_settings_toolbar_arrow_left", "drawable");
? "yt_outline_arrow_left_white_24" Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
: "yt_outline_arrow_left_black_24", drawable.setTint(Utils.getAppForegroundColor());
"drawable"); return drawable;
return Utils.getContext().getResources().getDrawable(backButtonResource); }
/**
* Sets the system navigation bar color for the activity.
* Applies the background color obtained from {@link Utils#getAppBackgroundColor()} to the navigation bar.
* For Android 10 (API 29) and above, enforces navigation bar contrast to ensure visibility.
*/
public static void setNavigationBarColor(@Nullable Window window) {
if (window == null) {
Logger.printDebug(() -> "Cannot set navigation bar color, window is null");
return;
}
window.setNavigationBarColor(Utils.getAppBackgroundColor());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.setNavigationBarContrastEnforced(true);
}
} }
/** /**
@@ -201,9 +217,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
// Set icon for the placeholder preference. // Set icon for the placeholder preference.
noResultsPreference.setLayoutResource(getResourceIdentifier( noResultsPreference.setLayoutResource(getResourceIdentifier(
"revanced_preference_with_icon_no_search_result", "layout")); "revanced_preference_with_icon_no_search_result", "layout"));
noResultsPreference.setIcon(getResourceIdentifier( noResultsPreference.setIcon(getResourceIdentifier("revanced_settings_search_icon", "drawable"));
ThemeHelper.isDarkTheme() ? "yt_outline_search_white_24" : "yt_outline_search_black_24",
"drawable"));
preferenceScreen.addPreference(noResultsPreference); preferenceScreen.addPreference(noResultsPreference);
} }
} }
@@ -226,7 +240,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
.getParent(); .getParent();
// Fix the system navigation bar color for submenus. // Fix the system navigation bar color for submenus.
ThemeHelper.setNavigationBarColor(preferenceScreenDialog.getWindow()); setNavigationBarColor(preferenceScreenDialog.getWindow());
// Fix edge-to-edge screen with Android 15 and YT 19.45+ // Fix edge-to-edge screen with Android 15 and YT 19.45+
// https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets // https://developer.android.com/develop/ui/views/layout/edge-to-edge#system-bars-insets
@@ -250,7 +264,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
TextView toolbarTextView = Utils.getChildView(toolbar, TextView toolbarTextView = Utils.getChildView(toolbar,
true, TextView.class::isInstance); true, TextView.class::isInstance);
if (toolbarTextView != null) { if (toolbarTextView != null) {
toolbarTextView.setTextColor(ThemeHelper.getForegroundColor()); toolbarTextView.setTextColor(Utils.getAppForegroundColor());
} }
LicenseActivityHook.setToolbarLayoutParams(toolbar); LicenseActivityHook.setToolbarLayoutParams(toolbar);
@@ -304,10 +318,10 @@ class AbstractPreferenceSearchData<T extends Preference> {
return text; return text;
} }
final int baseColor = ThemeHelper.getBackgroundColor(); final int baseColor = Utils.getAppBackgroundColor();
final int adjustedColor = ThemeHelper.isDarkTheme() final int adjustedColor = Utils.isDarkModeEnabled()
? ThemeHelper.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme. ? Utils.adjustColorBrightness(baseColor, 1.20f) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme. : Utils.adjustColorBrightness(baseColor, 0.95f); // Darken for light theme.
BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor); BackgroundColorSpan highlightSpan = new BackgroundColorSpan(adjustedColor);
SpannableStringBuilder spannable = new SpannableStringBuilder(text); SpannableStringBuilder spannable = new SpannableStringBuilder(text);

View File

@@ -1,32 +0,0 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
import app.revanced.extension.youtube.ThemeHelper;
@SuppressWarnings("unused")
public class ReVancedYouTubeAboutPreference extends ReVancedAboutPreference {
public int getLightColor() {
return ThemeHelper.getLightThemeColor();
}
public int getDarkColor() {
return ThemeHelper.getDarkThemeColor();
}
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReVancedYouTubeAboutPreference(Context context) {
super(context);
}
}

View File

@@ -2,9 +2,11 @@ package app.revanced.extension.youtube.sponsorblock;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.util.Pair;
import android.util.Patterns; import android.util.Patterns;
import android.widget.LinearLayout;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -56,7 +58,7 @@ public class SponsorBlockSettings {
} }
} }
for (int i = 0; i < categorySelectionsArray.length(); i++) { for (int i = 0, length = categorySelectionsArray.length(); i < length; i++) {
JSONObject categorySelectionObject = categorySelectionsArray.getJSONObject(i); JSONObject categorySelectionObject = categorySelectionsArray.getJSONObject(i);
String categoryKey = categorySelectionObject.getString("name"); String categoryKey = categorySelectionObject.getString("name");
@@ -181,13 +183,25 @@ public class SponsorBlockSettings {
// If user has a SponsorBlock user id then show a warning. // If user has a SponsorBlock user id then show a warning.
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId() if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId()
&& !Settings.SB_HIDE_EXPORT_WARNING.get()) { && !Settings.SB_HIDE_EXPORT_WARNING.get()) {
new AlertDialog.Builder(dialogContext) // Create the custom dialog.
.setMessage(str("revanced_sb_settings_revanced_export_user_id_warning")) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setNeutralButton(str("revanced_sb_settings_revanced_export_user_id_warning_dismiss"), dialogContext,
(dialog, which) -> Settings.SB_HIDE_EXPORT_WARNING.save(true)) null, // No title.
.setPositiveButton(android.R.string.ok, null) str("revanced_sb_settings_revanced_export_user_id_warning"), // Message.
.setCancelable(false) null, // No EditText.
.show(); null, // OK button text.
() -> {}, // OK button action (dismiss only).
null, // No cancel button action.
str("revanced_sb_settings_revanced_export_user_id_warning_dismiss"), // Neutral button text.
() -> Settings.SB_HIDE_EXPORT_WARNING.save(true), // Neutral button action.
true // Dismiss dialog when onNeutralClick.
);
// Set dialog as non-cancelable.
dialogPair.first.setCancelable(false);
// Show the dialog.
dialogPair.first.show();
} }
} }

View File

@@ -2,12 +2,12 @@ package app.revanced.extension.youtube.sponsorblock.objects;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifier; import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString; import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.getColorString;
import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor; import static app.revanced.extension.youtube.sponsorblock.objects.SegmentCategory.applyOpacityToColor;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
@@ -15,13 +15,10 @@ import android.preference.ListPreference;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Pair;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.*;
import android.widget.EditText;
import android.widget.GridLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
@@ -52,6 +49,7 @@ public class SegmentCategoryListPreference extends ListPreference {
private EditText dialogColorEditText; private EditText dialogColorEditText;
private EditText dialogOpacityEditText; private EditText dialogOpacityEditText;
private ColorPickerView dialogColorPickerView; private ColorPickerView dialogColorPickerView;
private Dialog dialog;
public SegmentCategoryListPreference(Context context, SegmentCategory category) { public SegmentCategoryListPreference(Context context, SegmentCategory category) {
super(context); super(context);
@@ -75,30 +73,45 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
@Override @Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { protected void showDialog(Bundle state) {
try { try {
Utils.setEditTextDialogTheme(builder); Context context = getContext();
categoryColor = category.getColorNoOpacity(); categoryColor = category.getColorNoOpacity();
categoryOpacity = category.getOpacity(); categoryOpacity = category.getOpacity();
selectedDialogEntryIndex = findIndexOfValue(getValue());
Context context = builder.getContext(); // Create the main layout for the dialog content.
LinearLayout mainLayout = new LinearLayout(context); LinearLayout contentLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL); contentLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setPadding(70, 0, 70, 0);
// Add behavior selection radio buttons.
RadioGroup radioGroup = new RadioGroup(context);
radioGroup.setOrientation(RadioGroup.VERTICAL);
CharSequence[] entries = getEntries();
for (int i = 0; i < entries.length; i++) {
RadioButton radioButton = new RadioButton(context);
radioButton.setText(entries[i]);
radioButton.setId(i);
radioButton.setChecked(i == selectedDialogEntryIndex);
radioGroup.addView(radioButton);
}
radioGroup.setOnCheckedChangeListener((group, checkedId) -> selectedDialogEntryIndex = checkedId);
radioGroup.setPadding(dipToPixels(10), 0, 0, 0);
contentLayout.addView(radioGroup);
// Inflate the color picker view. // Inflate the color picker view.
View colorPickerContainer = LayoutInflater.from(context) View colorPickerContainer = LayoutInflater.from(context)
.inflate(getResourceIdentifier("revanced_color_picker", "layout"), null); .inflate(getResourceIdentifier("revanced_color_picker", "layout"), null);
dialogColorPickerView = colorPickerContainer.findViewById( dialogColorPickerView = colorPickerContainer.findViewById(
getResourceIdentifier("color_picker_view", "id")); getResourceIdentifier("revanced_color_picker_view", "id"));
dialogColorPickerView.setColor(categoryColor); dialogColorPickerView.setColor(categoryColor);
mainLayout.addView(colorPickerContainer); contentLayout.addView(colorPickerContainer);
// Grid layout for color and opacity inputs. // Grid layout for color and opacity inputs.
GridLayout gridLayout = new GridLayout(context); GridLayout gridLayout = new GridLayout(context);
gridLayout.setColumnCount(3); gridLayout.setColumnCount(3);
gridLayout.setRowCount(2); gridLayout.setRowCount(2);
gridLayout.setPadding(dipToPixels(16), 0, 0, 0);
GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams(); GridLayout.LayoutParams gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(0); // First row. gridParams.rowSpec = GridLayout.spec(0); // First row.
@@ -111,7 +124,7 @@ public class SegmentCategoryListPreference extends ListPreference {
gridParams = new GridLayout.LayoutParams(); gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(0); // First row. gridParams.rowSpec = GridLayout.spec(0); // First row.
gridParams.columnSpec = GridLayout.spec(1); // Second column. gridParams.columnSpec = GridLayout.spec(1); // Second column.
gridParams.setMargins(0, 0, 10, 0); gridParams.setMargins(0, 0, dipToPixels(10), 0);
dialogColorDotView = new TextView(context); dialogColorDotView = new TextView(context);
dialogColorDotView.setLayoutParams(gridParams); dialogColorDotView.setLayoutParams(gridParams);
gridLayout.addView(dialogColorDotView); gridLayout.addView(dialogColorDotView);
@@ -162,8 +175,7 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
} }
}); });
dialogColorEditText.setLayoutParams(gridParams); gridLayout.addView(dialogColorEditText, gridParams);
gridLayout.addView(dialogColorEditText);
gridParams = new GridLayout.LayoutParams(); gridParams = new GridLayout.LayoutParams();
gridParams.rowSpec = GridLayout.spec(1); // Second row. gridParams.rowSpec = GridLayout.spec(1); // Second row.
@@ -226,11 +238,70 @@ public class SegmentCategoryListPreference extends ListPreference {
} }
} }
}); });
dialogOpacityEditText.setLayoutParams(gridParams); gridLayout.addView(dialogOpacityEditText, gridParams);
gridLayout.addView(dialogOpacityEditText);
updateOpacityText(); updateOpacityText();
mainLayout.addView(gridLayout); contentLayout.addView(gridLayout);
// Create ScrollView to wrap the content layout.
ScrollView contentScrollView = new ScrollView(context);
contentScrollView.setVerticalScrollBarEnabled(false); // Disable vertical scrollbar.
contentScrollView.setOverScrollMode(View.OVER_SCROLL_NEVER); // Disable overscroll effect.
LinearLayout.LayoutParams scrollViewParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1.0f
);
contentScrollView.setLayoutParams(scrollViewParams);
contentScrollView.addView(contentLayout);
// Create the custom dialog.
Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
category.title.toString(), // Title.
null, // No message (replaced by contentLayout).
null, // No EditText.
null, // OK button text.
() -> {
// OK button action.
if (selectedDialogEntryIndex >= 0 && getEntryValues() != null) {
String value = getEntryValues()[selectedDialogEntryIndex].toString();
if (callChangeListener(value)) {
setValue(value);
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
SegmentCategory.updateEnabledCategories();
}
try {
category.setColor(dialogColorEditText.getText().toString());
category.setOpacity(categoryOpacity);
} catch (IllegalArgumentException ex) {
Utils.showToastShort(str("revanced_settings_color_invalid"));
}
updateUI();
}
},
() -> {}, // Cancel button action (dismiss only).
str("revanced_settings_reset_color"), // Neutral button text.
() -> {
// Neutral button action (Reset).
try {
// Setting view color causes callback to update the UI.
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
categoryOpacity = category.getOpacityDefault();
updateOpacityText();
} catch (Exception ex) {
Logger.printException(() -> "resetButton onClick failure", ex);
}
},
false // Do not dismiss dialog on Neutral button click.
);
// Add the ScrollView to the dialog's main layout.
LinearLayout dialogMainLayout = dialogPair.second;
dialogMainLayout.addView(contentScrollView, dialogMainLayout.getChildCount() - 1);
// Set up color picker listener. // Set up color picker listener.
// Do last to prevent listener callbacks while setting up view. // Do last to prevent listener callbacks while setting up view.
@@ -247,69 +318,25 @@ public class SegmentCategoryListPreference extends ListPreference {
dialogColorEditText.setSelection(hexColor.length()); dialogColorEditText.setSelection(hexColor.length());
}); });
builder.setView(mainLayout); // Show the dialog.
builder.setTitle(category.title.toString()); dialog = dialogPair.first;
dialog.show();
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
onClick(dialog, DialogInterface.BUTTON_POSITIVE);
});
builder.setNeutralButton(str("revanced_settings_reset_color"), null);
builder.setNegativeButton(android.R.string.cancel, null);
selectedDialogEntryIndex = findIndexOfValue(getValue());
builder.setSingleChoiceItems(getEntries(), selectedDialogEntryIndex,
(dialog, which) -> selectedDialogEntryIndex = which);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "onPrepareDialogBuilder failure", ex); Logger.printException(() -> "showDialog failure", ex);
} }
} }
@Override
protected void showDialog(Bundle state) {
super.showDialog(state);
// Do not close dialog when reset is pressed.
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
button.setOnClickListener(view -> {
try {
// Setting view color causes callback to update the UI.
dialogColorPickerView.setColor(category.getColorNoOpacityDefault());
categoryOpacity = category.getOpacityDefault();
updateOpacityText();
} catch (Exception ex) {
Logger.printException(() -> "setOnClickListener failure", ex);
}
});
}
@Override @Override
protected void onDialogClosed(boolean positiveResult) { protected void onDialogClosed(boolean positiveResult) {
try { // Nullify dialog references.
if (positiveResult && selectedDialogEntryIndex >= 0 && getEntryValues() != null) { dialogColorDotView = null;
String value = getEntryValues()[selectedDialogEntryIndex].toString(); dialogColorEditText = null;
if (callChangeListener(value)) { dialogOpacityEditText = null;
setValue(value); dialogColorPickerView = null;
category.setBehaviour(Objects.requireNonNull(CategoryBehaviour.byReVancedKeyValue(value)));
SegmentCategory.updateEnabledCategories();
}
try { if (dialog != null) {
category.setColor(dialogColorEditText.getText().toString()); dialog.dismiss();
category.setOpacity(categoryOpacity); dialog = null;
} catch (IllegalArgumentException ex) {
Utils.showToastShort(str("revanced_settings_color_invalid"));
}
updateUI();
}
} catch (Exception ex) {
Logger.printException(() -> "onDialogClosed failure", ex);
} finally {
dialogColorDotView = null;
dialogColorEditText = null;
dialogOpacityEditText = null;
dialogColorPickerView = null;
} }
} }

View File

@@ -3,19 +3,25 @@ package app.revanced.extension.youtube.sponsorblock.ui;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.preference.*; import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup;
import android.preference.SwitchPreference;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -279,14 +285,26 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
addNewSegment.setOnPreferenceChangeListener((preference1, o) -> { addNewSegment.setOnPreferenceChangeListener((preference1, o) -> {
Boolean newValue = (Boolean) o; Boolean newValue = (Boolean) o;
if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) { if (newValue && !Settings.SB_SEEN_GUIDELINES.get()) {
new AlertDialog.Builder(preference1.getContext()) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(str("revanced_sb_guidelines_popup_title")) preference1.getContext(),
.setMessage(str("revanced_sb_guidelines_popup_content")) str("revanced_sb_guidelines_popup_title"), // Title.
.setNegativeButton(str("revanced_sb_guidelines_popup_already_read"), null) str("revanced_sb_guidelines_popup_content"), // Message.
.setPositiveButton(str("revanced_sb_guidelines_popup_open"), (dialogInterface, i) -> openGuidelines()) null, // No EditText.
.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true)) str("revanced_sb_guidelines_popup_open"), // OK button text.
.setCancelable(false) () -> openGuidelines(), // OK button action.
.show(); null, // Cancel button action.
str("revanced_sb_guidelines_popup_already_read"), // Neutral button text.
() -> {}, // Neutral button action (dismiss only).
true // Dismiss dialog when onNeutralClick.
);
// Set dialog as non-cancelable.
dialogPair.first.setCancelable(false);
dialogPair.first.setOnDismissListener(dialog -> Settings.SB_SEEN_GUIDELINES.save(true));
// Show the dialog.
dialogPair.first.show();
} }
Settings.SB_CREATE_NEW_SEGMENT.save(newValue); Settings.SB_CREATE_NEW_SEGMENT.save(newValue);
updateUI(); updateUI();
@@ -372,16 +390,52 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
generalCategory.addPreference(minSegmentDuration); generalCategory.addPreference(minSegmentDuration);
privateUserId = new EditTextPreference(context) { privateUserId = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { @Override
Utils.setEditTextDialogTheme(builder); protected void showDialog(Bundle state) {
try {
Context context = getContext();
EditText editText = getEditText();
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> { // Set initial EditText value to the current persisted value or empty string.
try { String initialValue = getText() != null ? getText() : "";
Utils.setClipboard(getEditText().getText()); editText.setText(initialValue);
} catch (Exception ex) { editText.setSelection(initialValue.length()); // Move cursor to end.
Logger.printException(() -> "Copy settings failure", ex);
} // Create custom dialog.
}); Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
context,
getTitle() != null ? getTitle().toString() : "", // Title.
null, // Message is replaced by EditText.
editText, // Pass the EditText.
null, // OK button text.
() -> {
// OK button action. Persist the EditText value when OK is clicked.
String newValue = editText.getText().toString();
if (callChangeListener(newValue)) {
setText(newValue);
}
},
() -> {}, // Cancel button action (dismiss only).
str("revanced_sb_settings_copy"), // Neutral button text (Copy).
() -> {
// Neutral button action (Copy).
try {
Utils.setClipboard(getEditText().getText());
} catch (Exception ex) {
Logger.printException(() -> "Copy settings failure", ex);
}
},
true // Dismiss dialog when onNeutralClick.
);
// Set dialog as cancelable.
dialogPair.first.setCancelable(true);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) {
Logger.printException(() -> "showDialog failure", ex);
}
} }
}; };
privateUserId.setTitle(str("revanced_sb_general_uuid")); privateUserId.setTitle(str("revanced_sb_general_uuid"));
@@ -407,51 +461,90 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI); editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI);
editText.setText(Settings.SB_API_URL.get()); editText.setText(Settings.SB_API_URL.get());
DialogInterface.OnClickListener urlChangeListener = (dialog, buttonPressed) -> { // Create a custom dialog.
if (buttonPressed == DialogInterface.BUTTON_NEUTRAL) { Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
Settings.SB_API_URL.resetToDefault(); context,
Utils.showToastLong(str("revanced_sb_api_url_reset")); str("revanced_sb_general_api_url"), // Title.
} else if (buttonPressed == DialogInterface.BUTTON_POSITIVE) { null, // No message, EditText replaces it.
String serverAddress = editText.getText().toString(); editText, // Pass the EditText.
if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) { null, // OK button text.
Utils.showToastLong(str("revanced_sb_api_url_invalid")); () -> {
} else if (!serverAddress.equals(Settings.SB_API_URL.get())) { // OK button action.
Settings.SB_API_URL.save(serverAddress); String serverAddress = editText.getText().toString();
Utils.showToastLong(str("revanced_sb_api_url_changed")); if (!SponsorBlockSettings.isValidSBServerAddress(serverAddress)) {
} Utils.showToastLong(str("revanced_sb_api_url_invalid"));
} } else if (!serverAddress.equals(Settings.SB_API_URL.get())) {
}; Settings.SB_API_URL.save(serverAddress);
new AlertDialog.Builder(context) Utils.showToastLong(str("revanced_sb_api_url_changed"));
.setTitle(apiUrl.getTitle()) }
.setView(editText) },
.setNegativeButton(android.R.string.cancel, null) () -> {}, // Cancel button action (dismiss dialog).
.setNeutralButton(str("revanced_settings_reset"), urlChangeListener) str("revanced_settings_reset"), // Neutral (Reset) button text.
.setPositiveButton(android.R.string.ok, urlChangeListener) () -> {
.show(); // Neutral button action.
Settings.SB_API_URL.resetToDefault();
Utils.showToastLong(str("revanced_sb_api_url_reset"));
},
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
return true; return true;
}); });
generalCategory.addPreference(apiUrl); generalCategory.addPreference(apiUrl);
importExport = new EditTextPreference(context) { importExport = new EditTextPreference(context) {
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) { @Override
Utils.setEditTextDialogTheme(builder); protected void showDialog(Bundle state) {
try {
Context context = getContext();
EditText editText = getEditText();
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> { // Create a custom dialog.
try { Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
Utils.setClipboard(getEditText().getText()); context,
} catch (Exception ex) { str("revanced_sb_settings_ie"), // Title.
Logger.printException(() -> "Copy settings failure", ex); null, // No message, EditText replaces it.
} editText, // Pass the EditText.
}); str("revanced_settings_import"), // OK button text.
() -> {
// OK button action. Trigger OnPreferenceChangeListener.
String newValue = editText.getText().toString();
if (getOnPreferenceChangeListener() != null) {
getOnPreferenceChangeListener().onPreferenceChange(this, newValue);
}
},
() -> {}, // Cancel button action (dismiss only).
str("revanced_sb_settings_copy"), // Neutral button text (Copy).
() -> {
// Neutral button action (Copy).
try {
Utils.setClipboard(editText.getText());
} catch (Exception ex) {
Logger.printException(() -> "Copy settings failure", ex);
}
},
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
} catch (Exception ex) {
Logger.printException(() -> "showDialog failure", ex);
}
} }
}; };
importExport.setTitle(str("revanced_sb_settings_ie")); importExport.setTitle(str("revanced_sb_settings_ie"));
// Summary is set in updateUI() // Summary is set in updateUI().
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT EditText editText = importExport.getEditText();
editText.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_MULTI_LINE | InputType.TYPE_TEXT_FLAG_MULTI_LINE
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
importExport.getEditText().setAutofillHints((String) null); editText.setAutofillHints((String) null);
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8); editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
// Set preference listeners.
importExport.setOnPreferenceClickListener(preference1 -> { importExport.setOnPreferenceClickListener(preference1 -> {
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings()); importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());
return true; return true;

View File

@@ -3,7 +3,7 @@ package app.revanced.extension.youtube.sponsorblock.ui;
import static android.text.Html.fromHtml; import static android.text.Html.fromHtml;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import android.app.AlertDialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
@@ -11,6 +11,8 @@ import android.preference.EditTextPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair;
import android.widget.LinearLayout;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -194,14 +196,26 @@ public class SponsorBlockStatsPreferenceCategory extends PreferenceCategory {
updateStatsSelfSaved.run(); updateStatsSelfSaved.run();
preference.setOnPreferenceClickListener(preference1 -> { preference.setOnPreferenceClickListener(preference1 -> {
new AlertDialog.Builder(preference1.getContext()) Pair<Dialog, LinearLayout> dialogPair = Utils.createCustomDialog(
.setTitle(str("revanced_sb_stats_self_saved_reset_title")) preference.getContext(),
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> { str("revanced_sb_stats_self_saved_reset_title"), // Title.
null, // No message.
null, // No EditText.
null, // OK button text.
() -> {
// OK button action.
Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault(); Settings.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.resetToDefault();
Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault(); Settings.SB_LOCAL_TIME_SAVED_MILLISECONDS.resetToDefault();
updateStatsSelfSaved.run(); updateStatsSelfSaved.run();
}) },
.setNegativeButton(android.R.string.no, null).show(); () -> {}, // Cancel button action (dismiss only).
null, // No neutral button.
null, // No neutral button action.
true // Dismiss dialog when onNeutralClick.
);
// Show the dialog.
dialogPair.first.show();
return true; return true;
}); });

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true org.gradle.parallel = true
android.useAndroidX = true android.useAndroidX = true
kotlin.code.style = official kotlin.code.style = official
version = 5.26.1-dev.2 version = 5.28.1-dev.2

View File

@@ -6,6 +6,10 @@ public final class app/revanced/patches/all/misc/adb/HideAdbPatchKt {
public static final fun getHideAdbStatusPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getHideAdbStatusPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/all/misc/appicon/HideAppIconPatchKt {
public static final fun getHideAppIconPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt { public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt {
public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch; public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -192,6 +196,10 @@ public final class app/revanced/patches/googlenews/misc/gms/GmsCoreSupportPatchK
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/googlephotos/misc/backup/EnableDCIMFoldersBackupControlPatchKt {
public static final fun getEnableDCIMFoldersBackupControlPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/googlephotos/misc/extension/ExtensionPatchKt { public final class app/revanced/patches/googlephotos/misc/extension/ExtensionPatchKt {
public static final fun getExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -284,6 +292,10 @@ public final class app/revanced/patches/messenger/inputfield/DisableTypingIndica
public static final fun getDisableTypingIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getDisableTypingIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/messenger/layout/HideFacebookButtonPatchKt {
public static final fun getHideFacebookButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/messenger/metaai/RemoveMetaAIPatchKt { public final class app/revanced/patches/messenger/metaai/RemoveMetaAIPatchKt {
public static final fun getRemoveMetaAIPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveMetaAIPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -682,6 +694,7 @@ public final class app/revanced/patches/shared/misc/pairip/license/DisableLicens
} }
public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt { public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt {
public static final fun overrideThemeColors (Ljava/lang/String;Ljava/lang/String;)V
public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch;
public static final fun settingsPatch (Lkotlin/Pair;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; public static final fun settingsPatch (Lkotlin/Pair;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch;
public static synthetic fun settingsPatch$default (Ljava/util/List;Ljava/util/Set;ILjava/lang/Object;)Lapp/revanced/patcher/patch/ResourcePatch; public static synthetic fun settingsPatch$default (Ljava/util/List;Ljava/util/Set;ILjava/lang/Object;)Lapp/revanced/patcher/patch/ResourcePatch;
@@ -909,6 +922,10 @@ public final class app/revanced/patches/spotify/misc/fix/login/FixFacebookLoginP
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/spotify/misc/lyrics/ChangeLyricsProviderPatchKt {
public static final fun getChangeLyricsProviderPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt { public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -941,6 +958,10 @@ public final class app/revanced/patches/swissid/integritycheck/RemoveGooglePlayI
public static final fun getRemoveGooglePlayIntegrityCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveGooglePlayIntegrityCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/threads/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/ticktick/misc/themeunlock/UnlockThemePatchKt { public final class app/revanced/patches/ticktick/misc/themeunlock/UnlockThemePatchKt {
public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getUnlockProPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -1662,6 +1683,7 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V
public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V
public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V
@@ -1671,6 +1693,7 @@ public final class app/revanced/util/BytecodeUtilsKt {
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;F)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;I)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;J)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V
public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V
public static final fun transformMethods (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V public static final fun transformMethods (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableClass;Lkotlin/jvm/functions/Function1;)V

View File

@@ -0,0 +1,48 @@
package app.revanced.patches.all.misc.appicon
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.asSequence
import app.revanced.util.childElementsSequence
import java.util.logging.Logger
import org.w3c.dom.Element
@Suppress("unused")
val hideAppIconPatch = resourcePatch(
name = "Hide app icon",
description = "Hides the app icon from the Android launcher.",
use = false,
) {
execute {
document("AndroidManifest.xml").use { document ->
var changed = false
val intentFilters = document.getElementsByTagName("intent-filter")
for (node in intentFilters.asSequence().filterIsInstance<Element>()) {
var hasMainAction = false
var launcherCategory: Element? = null
for (child in node.childElementsSequence()) {
when (child.tagName) {
"action" -> if (child.getAttribute("android:name") == "android.intent.action.MAIN") {
hasMainAction = true
}
"category" -> if (child.getAttribute("android:name") == "android.intent.category.LAUNCHER") {
launcherCategory = child
}
}
}
if (hasMainAction && launcherCategory != null) {
launcherCategory.setAttribute("android:name", "android.intent.category.DEFAULT")
changed = true
}
}
if (!changed) {
Logger.getLogger(this::class.java.name)
.warning("No changes made: Did not find any launcher intent-filters to change.")
}
}
}
}

View File

@@ -3,5 +3,5 @@ package app.revanced.patches.bandcamp.limitations
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
internal val handlePlaybackLimitsFingerprint = fingerprint { internal val handlePlaybackLimitsFingerprint = fingerprint {
strings("play limits processing track", "found play_count") strings("track_id", "play_count")
} }

View File

@@ -1,7 +1,7 @@
package app.revanced.patches.bandcamp.limitations package app.revanced.patches.bandcamp.limitations
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val removePlayLimitsPatch = bytecodePatch( val removePlayLimitsPatch = bytecodePatch(
@@ -11,6 +11,6 @@ val removePlayLimitsPatch = bytecodePatch(
compatibleWith("com.bandcamp.android") compatibleWith("com.bandcamp.android")
execute { execute {
handlePlaybackLimitsFingerprint.method.addInstructions(0, "return-void") handlePlaybackLimitsFingerprint.method.returnEarly()
} }
} }

View File

@@ -0,0 +1,18 @@
package app.revanced.patches.googlephotos.misc.backup
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val enableDCIMFoldersBackupControlPatch = bytecodePatch(
name = "Enable DCIM folders backup control",
description = "Disables always on backup for the Camera and other DCIM folders, allowing you to control backup " +
"for each folder individually. This will make the app default to having no folders backed up.",
use = false,
) {
compatibleWith("com.google.android.apps.photos")
execute {
isDCIMFolderBackupControlDisabled.method.returnEarly(false)
}
}

View File

@@ -0,0 +1,8 @@
package app.revanced.patches.googlephotos.misc.backup
import app.revanced.patcher.fingerprint
internal val isDCIMFolderBackupControlDisabled = fingerprint {
returns("Z")
strings("/dcim", "/mars_files/")
}

View File

@@ -1,7 +1,7 @@
package app.revanced.patches.idaustria.detection.signature package app.revanced.patches.idaustria.detection.signature
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val spoofSignaturePatch = bytecodePatch( val spoofSignaturePatch = bytecodePatch(
@@ -24,12 +24,6 @@ val spoofSignaturePatch = bytecodePatch(
"77ef1be61b2c01ebdabddcbf53cc4b6fd9a3c445606ee77b3758162c80ad8f8137b3c6864e92db904807dcb2be9d7717dd21" + "77ef1be61b2c01ebdabddcbf53cc4b6fd9a3c445606ee77b3758162c80ad8f8137b3c6864e92db904807dcb2be9d7717dd21" +
"bf42c121d620ddfb7914f7a95c713d9e1c1b7bdb4a03d618e40cf7e9e235c0b5687e03b7ab3,publicExponent=10001}" "bf42c121d620ddfb7914f7a95c713d9e1c1b7bdb4a03d618e40cf7e9e235c0b5687e03b7ab3,publicExponent=10001}"
spoofSignatureFingerprint.method.addInstructions( spoofSignatureFingerprint.method.returnEarly(expectedSignature)
0,
"""
const-string v0, "$expectedSignature"
return-object v0
""",
)
} }
} }

View File

@@ -1,9 +1,16 @@
package app.revanced.patches.instagram.ads package app.revanced.patches.instagram.ads
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.meta.ads.adInjectorFingerprint
import app.revanced.util.returnEarly
@Deprecated("Patch was moved to different package: app.revanced.patches.meta.ads.hideAdsPatch")
@Suppress("unused") @Suppress("unused")
val hideAdsPatch = bytecodePatch { val hideAdsPatch = bytecodePatch(
dependsOn(app.revanced.patches.meta.ads.hideAdsPatch) name = "Hide ads",
) {
compatibleWith("com.instagram.android")
execute {
adInjectorFingerprint.method.returnEarly(false)
}
} }

View File

@@ -0,0 +1,10 @@
package app.revanced.patches.messenger.layout
import app.revanced.patcher.fingerprint
internal val isFacebookButtonEnabledFingerprint = fingerprint {
parameters()
returns("Z")
strings("com.facebook.messaging.inbox.tab.plugins.core.tabtoolbarbutton." +
"facebookbutton.facebooktoolbarbutton.FacebookButtonTabButtonImplementation")
}

View File

@@ -0,0 +1,16 @@
package app.revanced.patches.messenger.layout
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val hideFacebookButtonPatch = bytecodePatch(
name = "Hide Facebook button",
description = "Hides the Facebook button in the top toolbar."
) {
compatibleWith("com.facebook.orca")
execute {
isFacebookButtonEnabledFingerprint.method.returnEarly(false)
}
}

View File

@@ -6,9 +6,20 @@ import app.revanced.patcher.fingerprint
internal val getMobileConfigBoolFingerprint = fingerprint { internal val getMobileConfigBoolFingerprint = fingerprint {
parameters("J") parameters("J")
returns("Z") returns("Z")
opcodes(Opcode.RETURN) opcodes(Opcode.RETURN)
custom { method, classDef -> custom { _, classDef ->
method.implementation ?: return@custom false // unsure if this is necessary
classDef.interfaces.contains("Lcom/facebook/mobileconfig/factory/MobileConfigUnsafeContext;") classDef.interfaces.contains("Lcom/facebook/mobileconfig/factory/MobileConfigUnsafeContext;")
} }
} }
internal val metaAIKillSwitchCheckFingerprint = fingerprint {
strings("SearchAiagentImplementationsKillSwitch")
opcodes(Opcode.CONST_WIDE)
}
internal val extensionMethodFingerprint = fingerprint {
strings("REPLACED_BY_PATCH")
custom { method, classDef ->
method.name == EXTENSION_METHOD_NAME && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
}

View File

@@ -2,11 +2,14 @@ package app.revanced.patches.messenger.metaai
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.messenger.misc.extension.sharedExtensionPatch import app.revanced.patches.messenger.misc.extension.sharedExtensionPatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/messenger/metaai/RemoveMetaAIPatch;" internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/messenger/metaai/RemoveMetaAIPatch;"
internal const val EXTENSION_METHOD_NAME = "overrideBooleanFlag"
@Suppress("unused") @Suppress("unused")
val removeMetaAIPatch = bytecodePatch( val removeMetaAIPatch = bytecodePatch(
@@ -25,10 +28,25 @@ val removeMetaAIPatch = bytecodePatch(
addInstructions( addInstructions(
returnIndex, returnIndex,
""" """
invoke-static { p1, p2, v$returnRegister }, $EXTENSION_CLASS_DESCRIPTOR->overrideConfigBool(JZ)Z invoke-static { p1, p2, v$returnRegister }, $EXTENSION_CLASS_DESCRIPTOR->$EXTENSION_METHOD_NAME(JZ)Z
move-result v$returnRegister move-result v$returnRegister
""" """
) )
} }
// Extract the common starting digits of Meta AI flag IDs from a flag found in code.
val relevantDigits = with(metaAIKillSwitchCheckFingerprint) {
method.getInstruction<WideLiteralInstruction>(patternMatch!!.startIndex).wideLiteral
}.toString().substring(0, 7)
// Replace placeholder in the extension method.
with(extensionMethodFingerprint) {
method.replaceInstruction(
stringMatches!!.first().index,
"""
const-string v1, "$relevantDigits"
"""
)
}
} }
} }

View File

@@ -2,4 +2,4 @@ package app.revanced.patches.messenger.misc.extension
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
val sharedExtensionPatch = sharedExtensionPatch("messenger", mainActivityOnCreateHook) val sharedExtensionPatch = sharedExtensionPatch("messenger", messengerApplicationOnCreateHook)

View File

@@ -2,6 +2,8 @@ package app.revanced.patches.messenger.misc.extension
import app.revanced.patches.shared.misc.extension.extensionHook import app.revanced.patches.shared.misc.extension.extensionHook
internal val mainActivityOnCreateHook = extensionHook { internal val messengerApplicationOnCreateHook = extensionHook {
strings("MainActivity_onCreate_begin") custom { method, classDef ->
method.name == "onCreate" && classDef.endsWith("/MessengerApplication;")
}
} }

View File

@@ -3,18 +3,9 @@ package app.revanced.patches.meta.ads
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly import app.revanced.util.returnEarly
@Deprecated("Instead use the Instagram or Threads specific hide ads patch")
@Suppress("unused") @Suppress("unused")
val hideAdsPatch = bytecodePatch( val hideAdsPatch = bytecodePatch {
name = "Hide ads",
) {
/**
* Patch is identical for both Instagram and Threads app.
*/
compatibleWith(
"com.instagram.android",
"com.instagram.barcelona",
)
execute { execute {
adInjectorFingerprint.method.returnEarly(false) adInjectorFingerprint.method.returnEarly(false)
} }

View File

@@ -1,7 +1,7 @@
package app.revanced.patches.nunl.firebase package app.revanced.patches.nunl.firebase
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val spoofCertificatePatch = bytecodePatch( val spoofCertificatePatch = bytecodePatch(
@@ -12,13 +12,7 @@ val spoofCertificatePatch = bytecodePatch(
execute { execute {
getFingerprintHashForPackageFingerprints.forEach { fingerprint -> getFingerprintHashForPackageFingerprints.forEach { fingerprint ->
fingerprint.method.addInstructions( fingerprint.method.returnEarly("eae41fc018df2731a9b6ae1ac327da44a288667b")
0,
"""
const-string v0, "eae41fc018df2731a9b6ae1ac327da44a288667b"
return-object v0
""",
)
} }
} }
} }

View File

@@ -1,12 +1,7 @@
package app.revanced.patches.pandora.ads package app.revanced.patches.pandora.ads
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.pandora.shared.constructUserDataFingerprint import app.revanced.util.returnEarly
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused") @Suppress("unused")
val disableAudioAdsPatch = bytecodePatch( val disableAudioAdsPatch = bytecodePatch(
@@ -15,16 +10,7 @@ val disableAudioAdsPatch = bytecodePatch(
compatibleWith("com.pandora.android") compatibleWith("com.pandora.android")
execute { execute {
constructUserDataFingerprint.method.apply { getIsAdSupportedFingerprint.method.returnEarly(false)
// First match is "hasAudioAds". requestAudioAdFingerprint.method.returnEarly()
val hasAudioAdsStringIndex = constructUserDataFingerprint.stringMatches!!.first().index
val moveResultIndex = indexOfFirstInstructionOrThrow(hasAudioAdsStringIndex, Opcode.MOVE_RESULT)
val hasAudioAdsRegister = getInstruction<OneRegisterInstruction>(moveResultIndex).registerA
addInstruction(
moveResultIndex + 1,
"const/4 v$hasAudioAdsRegister, 0"
)
}
} }
} }

View File

@@ -0,0 +1,15 @@
package app.revanced.patches.pandora.ads
import app.revanced.patcher.fingerprint
internal val getIsAdSupportedFingerprint = fingerprint {
custom { method, classDef ->
method.name == "getIsAdSupported" && classDef.endsWith("UserData;")
}
}
internal val requestAudioAdFingerprint = fingerprint {
custom { method, classDef ->
method.name == "requestAudioAdFromAdSDK" && classDef.endsWith("ContentServiceOpsImpl;")
}
}

View File

@@ -1,12 +1,7 @@
package app.revanced.patches.pandora.misc package app.revanced.patches.pandora.misc
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.pandora.shared.constructUserDataFingerprint import app.revanced.util.returnEarly
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused") @Suppress("unused")
val enableUnlimitedSkipsPatch = bytecodePatch( val enableUnlimitedSkipsPatch = bytecodePatch(
@@ -15,17 +10,6 @@ val enableUnlimitedSkipsPatch = bytecodePatch(
compatibleWith("com.pandora.android") compatibleWith("com.pandora.android")
execute { execute {
constructUserDataFingerprint.method.apply { skipLimitBehaviorFingerprint.method.returnEarly("unlimited")
// Last match is "skipLimitBehavior".
val skipLimitBehaviorStringIndex = constructUserDataFingerprint.stringMatches!!.last().index
val moveResultObjectIndex =
indexOfFirstInstructionOrThrow(skipLimitBehaviorStringIndex, Opcode.MOVE_RESULT_OBJECT)
val skipLimitBehaviorRegister = getInstruction<OneRegisterInstruction>(moveResultObjectIndex).registerA
addInstruction(
moveResultObjectIndex + 1,
"const-string v$skipLimitBehaviorRegister, \"unlimited\""
)
}
} }
} }

View File

@@ -0,0 +1,9 @@
package app.revanced.patches.pandora.misc
import app.revanced.patcher.fingerprint
internal val skipLimitBehaviorFingerprint = fingerprint {
custom { method, classDef ->
method.name == "getSkipLimitBehavior" && classDef.endsWith("UserData;")
}
}

View File

@@ -1,7 +0,0 @@
package app.revanced.patches.pandora.shared
import app.revanced.patcher.fingerprint
internal val constructUserDataFingerprint = fingerprint {
strings("hasAudioAds", "skipLimitBehavior")
}

View File

@@ -1,8 +1,8 @@
package app.revanced.patches.photomath.detection.deviceid package app.revanced.patches.photomath.detection.deviceid
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.photomath.detection.signature.signatureDetectionPatch import app.revanced.patches.photomath.detection.signature.signatureDetectionPatch
import app.revanced.util.returnEarly
import kotlin.random.Random import kotlin.random.Random
@Suppress("unused") @Suppress("unused")
@@ -15,12 +15,6 @@ val getDeviceIdPatch = bytecodePatch(
compatibleWith("com.microblink.photomath") compatibleWith("com.microblink.photomath")
execute { execute {
getDeviceIdFingerprint.method.replaceInstructions( getDeviceIdFingerprint.method.returnEarly(Random.nextLong().toString(16))
0,
"""
const-string v0, "${Random.nextLong().toString(16)}"
return-object v0
""",
)
} }
} }

View File

@@ -3,6 +3,7 @@ package app.revanced.patches.piccomafr.misc
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.util.returnEarly
@Suppress("unused") @Suppress("unused")
val spoofAndroidDeviceIdPatch = bytecodePatch( val spoofAndroidDeviceIdPatch = bytecodePatch(
@@ -39,12 +40,6 @@ val spoofAndroidDeviceIdPatch = bytecodePatch(
) { it!!.matches("[A-Fa-f0-9]{16}".toRegex()) } ) { it!!.matches("[A-Fa-f0-9]{16}".toRegex()) }
execute { execute {
getAndroidIdFingerprint.method.addInstructions( getAndroidIdFingerprint.method.returnEarly(androidDeviceId!!)
0,
"""
const-string v0, "$androidDeviceId"
return-object v0
""",
)
} }
} }

View File

@@ -1,9 +1,9 @@
package app.revanced.patches.reddit.customclients.boostforreddit.api package app.revanced.patches.reddit.customclients.boostforreddit.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
val spoofClientPatch = spoofClientPatch(redirectUri = "http://rubenmayayo.com") { clientIdOption -> val spoofClientPatch = spoofClientPatch(redirectUri = "http://rubenmayayo.com") { clientIdOption ->
@@ -14,13 +14,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "http://rubenmayayo.com")
execute { execute {
// region Patch client id. // region Patch client id.
getClientIdFingerprint.method.addInstructions( getClientIdFingerprint.method.returnEarly(clientId!!)
0,
"""
const-string v0, "$clientId"
return-object v0
""",
)
// endregion // endregion

View File

@@ -1,9 +1,8 @@
package app.revanced.patches.reddit.customclients.joeyforreddit.api package app.revanced.patches.reddit.customclients.joeyforreddit.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstructions
import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.disablePiracyDetectionPatch import app.revanced.patches.reddit.customclients.joeyforreddit.detection.piracy.disablePiracyDetectionPatch
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.util.returnEarly
val spoofClientPatch = spoofClientPatch(redirectUri = "https://127.0.0.1:65023/authorize_callback") { clientIdOption -> val spoofClientPatch = spoofClientPatch(redirectUri = "https://127.0.0.1:65023/authorize_callback") { clientIdOption ->
dependsOn(disablePiracyDetectionPatch) dependsOn(disablePiracyDetectionPatch)
@@ -19,13 +18,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "https://127.0.0.1:65023/a
execute { execute {
// region Patch client id. // region Patch client id.
getClientIdFingerprint.method.addInstructions( getClientIdFingerprint.method.returnEarly(clientId!!)
0,
"""
const-string v0, "$clientId"
return-object v0
""",
)
// endregion // endregion
@@ -35,13 +28,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "https://127.0.0.1:65023/a
val randomName = (0..100000).random() val randomName = (0..100000).random()
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)" val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
authUtilityUserAgentFingerprint.method.replaceInstructions( authUtilityUserAgentFingerprint.method.returnEarly(userAgent)
0,
"""
const-string v0, "$userAgent"
return-object v0
""",
)
// endregion // endregion
} }

View File

@@ -2,12 +2,12 @@ package app.revanced.patches.reddit.customclients.redditisfun.api
import app.revanced.patcher.Fingerprint import app.revanced.patcher.Fingerprint
import app.revanced.patcher.Match import app.revanced.patcher.Match
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
@@ -54,13 +54,7 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "redditisfun://auth") { cl
val randomName = (0..100000).random() val randomName = (0..100000).random()
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)" val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
getUserAgentFingerprint.method.addInstructions( getUserAgentFingerprint.method.returnEarly(userAgent)
0,
"""
const-string v0, "$userAgent"
return-object v0
""",
)
// endregion // endregion

View File

@@ -1,7 +1,7 @@
package app.revanced.patches.reddit.customclients.slide.api package app.revanced.patches.reddit.customclients.slide.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.util.returnEarly
val spoofClientPatch = spoofClientPatch(redirectUri = "http://www.ccrama.me") { clientIdOption -> val spoofClientPatch = spoofClientPatch(redirectUri = "http://www.ccrama.me") { clientIdOption ->
compatibleWith("me.ccrama.redditslide") compatibleWith("me.ccrama.redditslide")
@@ -9,12 +9,6 @@ val spoofClientPatch = spoofClientPatch(redirectUri = "http://www.ccrama.me") {
val clientId by clientIdOption val clientId by clientIdOption
execute { execute {
getClientIdFingerprint.method.addInstructions( getClientIdFingerprint.method.returnEarly(clientId!!)
0,
"""
const-string v0, "$clientId"
return-object v0
""",
)
} }
} }

View File

@@ -1,14 +1,14 @@
package app.revanced.patches.reddit.customclients.sync.syncforreddit.api package app.revanced.patches.reddit.customclients.sync.syncforreddit.api
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patches.reddit.customclients.spoofClientPatch import app.revanced.patches.reddit.customclients.spoofClientPatch
import app.revanced.patches.reddit.customclients.sync.detection.piracy.disablePiracyDetectionPatch import app.revanced.patches.reddit.customclients.sync.detection.piracy.disablePiracyDetectionPatch
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference import com.android.tools.smali.dexlib2.iface.reference.StringReference
import java.util.* import java.util.Base64
val spoofClientPatch = spoofClientPatch( val spoofClientPatch = spoofClientPatch(
redirectUri = "http://redditsync/auth", redirectUri = "http://redditsync/auth",
@@ -28,13 +28,8 @@ val spoofClientPatch = spoofClientPatch(
getBearerTokenFingerprint.match(getAuthorizationStringFingerprint.originalClassDef).method.apply { getBearerTokenFingerprint.match(getAuthorizationStringFingerprint.originalClassDef).method.apply {
val auth = Base64.getEncoder().encodeToString("$clientId:".toByteArray(Charsets.UTF_8)) val auth = Base64.getEncoder().encodeToString("$clientId:".toByteArray(Charsets.UTF_8))
addInstructions( returnEarly("Basic $auth")
0,
"""
const-string v0, "Basic $auth"
return-object v0
""",
)
val occurrenceIndex = val occurrenceIndex =
getAuthorizationStringFingerprint.stringMatches!!.first().index getAuthorizationStringFingerprint.stringMatches!!.first().index
@@ -63,23 +58,19 @@ val spoofClientPatch = spoofClientPatch(
val randomName = (0..100000).random() val randomName = (0..100000).random()
val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)" val userAgent = "$randomName:app.revanced.$randomName:v1.0.0 (by /u/revanced)"
getUserAgentFingerprint.method.replaceInstruction( getUserAgentFingerprint.method.returnEarly(userAgent)
0,
"""
const-string v0, "$userAgent"
return-object v0
""",
)
// endregion // endregion
// region Patch Imgur API URL. // region Patch Imgur API URL.
val apiUrlIndex = imgurImageAPIFingerprint.stringMatches!!.first().index imgurImageAPIFingerprint.let {
imgurImageAPIFingerprint.method.replaceInstruction( val apiUrlIndex = it.stringMatches!!.first().index
apiUrlIndex, it.method.replaceInstruction(
"const-string v1, \"https://api.imgur.com/3/image\"", apiUrlIndex,
) "const-string v1, \"https://api.imgur.com/3/image\"",
)
}
// endregion // endregion
} }

View File

@@ -84,7 +84,7 @@ fun checkEnvironmentPatch(
fun invokeCheck() = mainActivityOnCreateFingerprint.method.addInstruction( fun invokeCheck() = mainActivityOnCreateFingerprint.method.addInstruction(
0, 0,
"invoke-static/range { p0 .. p0 },$EXTENSION_CLASS_DESCRIPTOR->check(Landroid/app/Activity;)V", "invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->check(Landroid/app/Activity;)V",
) )
setPatchInfo() setPatchInfo()

View File

@@ -3,11 +3,11 @@ package app.revanced.patches.shared.misc.extension
import app.revanced.patcher.Fingerprint import app.revanced.patcher.Fingerprint
import app.revanced.patcher.FingerprintBuilder import app.revanced.patcher.FingerprintBuilder
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import java.net.URLDecoder import java.net.URLDecoder
import java.util.jar.JarFile import java.util.jar.JarFile
@@ -80,14 +80,7 @@ fun sharedExtensionPatch(
} }
val manifestValue = getPatchesManifestEntry("Version") val manifestValue = getPatchesManifestEntry("Version")
returnEarly(manifestValue)
addInstructions(
0,
"""
const-string v0, "$manifestValue"
return-object v0
""",
)
} }
} }
} }

View File

@@ -0,0 +1,23 @@
package app.revanced.patches.shared.misc.settings
import app.revanced.patcher.fingerprint
import app.revanced.patches.shared.misc.extension.EXTENSION_CLASS_DESCRIPTOR
import com.android.tools.smali.dexlib2.AccessFlags
internal val themeLightColorResourceNameFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters()
custom { method, classDef ->
method.name == "getThemeLightColorResourceName" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
}
internal val themeDarkColorResourceNameFingerprint = fingerprint {
accessFlags(AccessFlags.PRIVATE, AccessFlags.STATIC)
returns("Ljava/lang/String;")
parameters()
custom { method, classDef ->
method.name == "getThemeDarkColorResourceName" && classDef.type == EXTENSION_CLASS_DESCRIPTOR
}
}

View File

@@ -1,6 +1,7 @@
package app.revanced.patches.shared.misc.settings package app.revanced.patches.shared.misc.settings
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResource import app.revanced.patches.all.misc.resources.addResource
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
@@ -13,6 +14,7 @@ import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
import app.revanced.util.getNode import app.revanced.util.getNode
import app.revanced.util.insertFirst import app.revanced.util.insertFirst
import app.revanced.util.returnEarly
import org.w3c.dom.Node import org.w3c.dom.Node
// TODO: Delete this on next major version bump. // TODO: Delete this on next major version bump.
@@ -22,6 +24,30 @@ fun settingsPatch (
preferences: Set<BasePreference>, preferences: Set<BasePreference>,
) = settingsPatch(listOf(rootPreference), preferences) ) = settingsPatch(listOf(rootPreference), preferences)
private var themeForegroundColor : String? = null
private var themeBackgroundColor : String? = null
/**
* Sets the default theme colors used in various ReVanced specific settings menus.
* By default these colors are white and black, but instead can be set to the
* same color the target app uses for it's own settings.
*/
fun overrideThemeColors(foregroundColor: String, backgroundColor: String) {
themeForegroundColor = foregroundColor
themeBackgroundColor = backgroundColor
}
private val settingsColorPatch = bytecodePatch {
finalize {
if (themeForegroundColor != null) {
themeLightColorResourceNameFingerprint.method.returnEarly(themeForegroundColor!!)
}
if (themeBackgroundColor != null) {
themeDarkColorResourceNameFingerprint.method.returnEarly(themeBackgroundColor!!)
}
}
}
/** /**
* A resource patch that adds settings to a settings fragment. * A resource patch that adds settings to a settings fragment.
* *
@@ -33,12 +59,28 @@ fun settingsPatch (
rootPreferences: List<Pair<BasePreference, String>>? = null, rootPreferences: List<Pair<BasePreference, String>>? = null,
preferences: Set<BasePreference>, preferences: Set<BasePreference>,
) = resourcePatch { ) = resourcePatch {
dependsOn(addResourcesPatch) dependsOn(addResourcesPatch, settingsColorPatch)
execute { execute {
copyResources( copyResources(
"settings", "settings",
ResourceGroup("xml", "revanced_prefs.xml", "revanced_prefs_icons.xml"), ResourceGroup("xml", "revanced_prefs.xml", "revanced_prefs_icons.xml"),
ResourceGroup("drawable",
// CustomListPreference resources.
"revanced_ic_dialog_alert.xml",
"revanced_settings_arrow_time.xml",
"revanced_settings_circle_background.xml",
"revanced_settings_cursor.xml",
"revanced_settings_custom_checkmark.xml",
"revanced_settings_search_icon.xml",
"revanced_settings_toolbar_arrow_left.xml",
),
ResourceGroup("layout",
"revanced_custom_list_item_checked.xml",
// Color picker.
"revanced_color_dot_widget.xml",
"revanced_color_picker.xml",
)
) )
addResources("shared", "misc.settings.settingsResourcePatch") addResources("shared", "misc.settings.settingsResourcePatch")

View File

@@ -12,7 +12,7 @@ import org.w3c.dom.Document
* @param summaryKey The preference summary key. * @param summaryKey The preference summary key.
* @param icon The preference icon resource name. * @param icon The preference icon resource name.
* @param layout Layout declaration. * @param layout Layout declaration.
* @param tag The preference tag. * @param tag The preference class type.
* @param entriesKey The entries array key. * @param entriesKey The entries array key.
* @param entryValuesKey The entry values array key. * @param entryValuesKey The entry values array key.
*/ */
@@ -20,10 +20,12 @@ import org.w3c.dom.Document
class ListPreference( class ListPreference(
key: String? = null, key: String? = null,
titleKey: String = "${key}_title", titleKey: String = "${key}_title",
summaryKey: String? = "${key}_summary", /** Summary key is ignored and will be removed soon */
//@Deprecated
summaryKey: String? = null,
icon: String? = null, icon: String? = null,
layout: String? = null, layout: String? = null,
tag: String = "ListPreference", tag: String = "app.revanced.extension.shared.settings.preference.CustomDialogListPreference",
val entriesKey: String? = "${key}_entries", val entriesKey: String? = "${key}_entries",
val entryValuesKey: String? = "${key}_entry_values" val entryValuesKey: String? = "${key}_entry_values"
) : BasePreference(key, titleKey, summaryKey, icon, layout, tag) { ) : BasePreference(key, titleKey, summaryKey, icon, layout, tag) {

View File

@@ -72,7 +72,7 @@ val hideCreateButtonPatch = bytecodePatch(
if (oldNavigationBarAddItemMethod != null) { if (oldNavigationBarAddItemMethod != null) {
// In case an older version of the app is being patched, hook the old method which adds navigation bar items. // In case an older version of the app is being patched, hook the old method which adds navigation bar items.
// Return null early if the navigation bar item title resource id is the old Create button title resource id. // Return early if the navigation bar item title resource id is the old Create button title resource id.
oldNavigationBarAddItemFingerprint.methodOrNull?.apply { oldNavigationBarAddItemFingerprint.methodOrNull?.apply {
val getNavigationBarItemTitleStringIndex = indexOfFirstInstructionOrThrow { val getNavigationBarItemTitleStringIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>() val reference = getReference<MethodReference>()
@@ -89,18 +89,26 @@ val hideCreateButtonPatch = bytecodePatch(
val isOldCreateButtonDescriptor = val isOldCreateButtonDescriptor =
"$EXTENSION_CLASS_DESCRIPTOR->isOldCreateButton(I)Z" "$EXTENSION_CLASS_DESCRIPTOR->isOldCreateButton(I)Z"
val returnEarlyInstruction = if (returnType == "V") {
// In older implementations the method return value is void.
"return-void"
} else {
// In newer implementations
// return null because the method return value is a BottomNavigationItemView.
"const/4 v0, 0\n" +
"return-object v0"
}
addInstructionsWithLabels( addInstructionsWithLabels(
0, 0,
""" """
invoke-static { v$oldNavigationBarItemTitleResIdRegister }, $isOldCreateButtonDescriptor invoke-static { v$oldNavigationBarItemTitleResIdRegister }, $isOldCreateButtonDescriptor
move-result v0 move-result v0
# If this navigation bar item is not the Create button, jump to the normal method logic. # If this navigation bar item is not the Create button, jump to the normal method logic.
if-eqz v0, :normal-method-logic if-eqz v0, :normal-method-logic
# Return null early because this method return value is a BottomNavigationItemView. $returnEarlyInstruction
const/4 v0, 0
return-object v0
""", """,
ExternalLabel("normal-method-logic", firstInstruction) ExternalLabel("normal-method-logic", firstInstruction)
) )

View File

@@ -19,7 +19,7 @@ val fixFacebookLoginPatch = bytecodePatch(
// signature checks. // signature checks.
val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassFingerprint.originalClassDef val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassFingerprint.originalClassDef
// Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to // Always return 0 (no Intent was launched) as the result of trying to authorize with the Facebook app to
// make the login fallback to a web browser window. // make the login fallback to a web browser window.
katanaProxyLoginMethodTryAuthorizeFingerprint katanaProxyLoginMethodTryAuthorizeFingerprint
.match(katanaProxyLoginMethodHandlerClass) .match(katanaProxyLoginMethodHandlerClass)

View File

@@ -0,0 +1,123 @@
package app.revanced.patches.spotify.misc.lyrics
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction35c
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
import java.net.InetAddress
import java.net.URI
import java.net.URISyntaxException
import java.net.UnknownHostException
import java.util.logging.Logger
@Suppress("unused")
val changeLyricsProviderPatch = bytecodePatch(
name = "Change lyrics provider",
description = "Changes the lyrics provider to a custom one.",
use = false,
) {
compatibleWith("com.spotify.music")
val lyricsProviderHost by stringOption(
key = "lyricsProviderHost",
default = "lyrics.natanchiodi.fr",
title = "Lyrics provider host",
description = "The domain name or IP address of a custom lyrics provider.",
required = false,
) {
// Fix bad data if the user enters a URL (https://whatever.com/path).
val host = try {
URI(it!!).host ?: it
} catch (e: URISyntaxException) {
return@stringOption false
}
// Do a courtesy check if the host can be resolved.
// If it does not resolve, then print a warning but use the host anyway.
// Unresolvable hosts should not be rejected, since the patching environment
// may not allow network connections or the network may be down.
try {
InetAddress.getByName(host)
} catch (e: UnknownHostException) {
Logger.getLogger(this::class.java.name).warning(
"Host \"$host\" did not resolve to any domain."
)
}
true
}
execute {
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
Logger.getLogger(this::class.java.name).severe(
"Change lyrics provider patch is not supported for this target version."
)
return@execute
}
val httpClientBuilderMethod = httpClientBuilderFingerprint.originalMethod
// region Create a modified copy of the HTTP client builder method with the custom lyrics provider host.
val patchedHttpClientBuilderMethod = with(httpClientBuilderMethod) {
val invokeBuildUrlIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.returnType == "Lokhttp3/HttpUrl;"
}
val setUrlBuilderHostIndex = indexOfFirstInstructionReversedOrThrow(invokeBuildUrlIndex) {
val reference = getReference<MethodReference>()
reference?.definingClass == "Lokhttp3/HttpUrl${"$"}Builder;" &&
reference.parameterTypes.firstOrNull() == "Ljava/lang/String;"
}
val hostRegister = getInstruction<FiveRegisterInstruction>(setUrlBuilderHostIndex).registerD
MutableMethod(this).apply {
name = "rv_getCustomLyricsProviderHttpClient"
addInstruction(
setUrlBuilderHostIndex,
"const-string v$hostRegister, \"$lyricsProviderHost\""
)
// Add the patched method to the class.
httpClientBuilderFingerprint.classDef.methods.add(this)
}
}
//endregion
// region Replace the call to the HTTP client builder method used exclusively for lyrics by the modified one.
getLyricsHttpClientFingerprint(httpClientBuilderMethod).method.apply {
val getLyricsHttpClientIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>() == httpClientBuilderMethod
}
val getLyricsHttpClientInstruction = getInstruction<BuilderInstruction35c>(getLyricsHttpClientIndex)
// Replace the original method call with a call to our patched method.
replaceInstruction(
getLyricsHttpClientIndex,
BuilderInstruction35c(
getLyricsHttpClientInstruction.opcode,
getLyricsHttpClientInstruction.registerCount,
getLyricsHttpClientInstruction.registerC,
getLyricsHttpClientInstruction.registerD,
getLyricsHttpClientInstruction.registerE,
getLyricsHttpClientInstruction.registerF,
getLyricsHttpClientInstruction.registerG,
ImmutableMethodReference(
patchedHttpClientBuilderMethod.definingClass,
patchedHttpClientBuilderMethod.name, // Only difference from the original method.
patchedHttpClientBuilderMethod.parameters,
patchedHttpClientBuilderMethod.returnType
)
)
)
}
//endregion
}
}

View File

@@ -0,0 +1,21 @@
package app.revanced.patches.spotify.misc.lyrics
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val httpClientBuilderFingerprint = fingerprint {
strings("client == null", "scheduler == null")
}
internal fun getLyricsHttpClientFingerprint(httpClientBuilderMethodReference: MethodReference) =
fingerprint {
returns(httpClientBuilderMethodReference.returnType)
parameters()
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<MethodReference>() == httpClientBuilderMethodReference
} >= 0
}
}

View File

@@ -3,6 +3,7 @@ package app.revanced.patches.spotify.misc.privacy
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.util.literal import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val shareCopyUrlFingerprint = fingerprint { internal val shareCopyUrlFingerprint = fingerprint {
returns("Ljava/lang/Object;") returns("Ljava/lang/Object;")
@@ -23,9 +24,15 @@ internal val shareCopyUrlLegacyFingerprint = fingerprint {
} }
internal val formatAndroidShareSheetUrlFingerprint = fingerprint { internal val formatAndroidShareSheetUrlFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("Ljava/lang/String;") returns("Ljava/lang/String;")
parameters("L", "Ljava/lang/String;") parameters("L", "Ljava/lang/String;")
opcodes(
Opcode.GOTO,
Opcode.IF_EQZ,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT
)
literal { literal {
'\n'.code.toLong() '\n'.code.toLong()
} }

View File

@@ -8,6 +8,7 @@ import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -56,7 +57,15 @@ val sanitizeSharingLinksPatch = bytecodePatch(
shareUrlParameter = "p2" shareUrlParameter = "p2"
} else { } else {
shareSheetFingerprint = formatAndroidShareSheetUrlFingerprint shareSheetFingerprint = formatAndroidShareSheetUrlFingerprint
shareUrlParameter = "p1" val methodAccessFlags = formatAndroidShareSheetUrlFingerprint.originalMethod.accessFlags
shareUrlParameter = if (AccessFlags.STATIC.isSet(methodAccessFlags)) {
// In newer implementations the method is static, so p0 is not `this`.
"p1"
} else {
// In older implementations the method is not static, making it so p0 is `this`.
// For that reason, add one to the parameter register.
"p2"
}
} }
shareSheetFingerprint.method.addInstructions( shareSheetFingerprint.method.addInstructions(

View File

@@ -0,0 +1,16 @@
package app.revanced.patches.threads
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.meta.ads.adInjectorFingerprint
import app.revanced.util.returnEarly
@Suppress("unused")
val hideAdsPatch = bytecodePatch(
name = "Hide ads",
) {
compatibleWith("com.instagram.barcelona"("382.0.0.51.85"))
execute {
adInjectorFingerprint.method.returnEarly(false)
}
}

View File

@@ -25,7 +25,7 @@ val embeddedAdsPatch = bytecodePatch(
addResources("twitch", "ad.embedded.embeddedAdsPatch") addResources("twitch", "ad.embedded.embeddedAdsPatch")
PreferenceScreen.ADS.SURESTREAM.addPreferences( PreferenceScreen.ADS.SURESTREAM.addPreferences(
ListPreference("revanced_block_embedded_ads", summaryKey = null), ListPreference("revanced_block_embedded_ads"),
) )
// Inject OkHttp3 application interceptor // Inject OkHttp3 application interceptor

View File

@@ -34,10 +34,7 @@ val showDeletedMessagesPatch = bytecodePatch(
addResources("twitch", "chat.antidelete.showDeletedMessagesPatch") addResources("twitch", "chat.antidelete.showDeletedMessagesPatch")
PreferenceScreen.CHAT.GENERAL.addPreferences( PreferenceScreen.CHAT.GENERAL.addPreferences(
ListPreference( ListPreference("revanced_show_deleted_messages")
key = "revanced_show_deleted_messages",
summaryKey = null,
),
) )
// Spoiler mode: Force set hasModAccess member to true in constructor // Spoiler mode: Force set hasModAccess member to true in constructor

View File

@@ -1,7 +1,7 @@
package app.revanced.patches.warnwetter.misc.firebasegetcert package app.revanced.patches.warnwetter.misc.firebasegetcert
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
val firebaseGetCertPatch = bytecodePatch( val firebaseGetCertPatch = bytecodePatch(
description = "Spoofs the X-Android-Cert header.", description = "Spoofs the X-Android-Cert header.",
@@ -10,13 +10,7 @@ val firebaseGetCertPatch = bytecodePatch(
execute { execute {
listOf(getRegistrationCertFingerprint, getMessagingCertFingerprint).forEach { match -> listOf(getRegistrationCertFingerprint, getMessagingCertFingerprint).forEach { match ->
match.method.addInstructions( match.method.returnEarly("0799DDF0414D3B3475E88743C91C0676793ED450")
0,
"""
const-string v0, "0799DDF0414D3B3475E88743C91C0676793ED450"
return-object v0
""",
)
} }
} }
} }

View File

@@ -77,8 +77,6 @@ val hideAdsPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -25,8 +25,6 @@ val hideGetPremiumPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -23,8 +23,6 @@ val videoAdsPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -53,8 +53,6 @@ val copyVideoUrlPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -24,8 +24,6 @@ val removeViewerDiscretionDialogPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -11,10 +11,14 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPref
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.playercontrols.* import app.revanced.patches.youtube.misc.playercontrols.addBottomControl
import app.revanced.patches.youtube.misc.playercontrols.initializeBottomControl
import app.revanced.patches.youtube.misc.playercontrols.injectVisibilityCheckCall
import app.revanced.patches.youtube.misc.playercontrols.playerControlsPatch
import app.revanced.patches.youtube.misc.playercontrols.playerControlsResourcePatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityFingerprint import app.revanced.patches.youtube.shared.mainActivityOnCreateFingerprint
import app.revanced.patches.youtube.video.information.videoInformationPatch import app.revanced.patches.youtube.video.information.videoInformationPatch
import app.revanced.util.ResourceGroup import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources import app.revanced.util.copyResources
@@ -68,8 +72,6 @@ val downloadsPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
@@ -83,12 +85,10 @@ val downloadsPatch = bytecodePatch(
injectVisibilityCheckCall(BUTTON_DESCRIPTOR) injectVisibilityCheckCall(BUTTON_DESCRIPTOR)
// Main activity is used to launch downloader intent. // Main activity is used to launch downloader intent.
mainActivityFingerprint.method.apply { mainActivityOnCreateFingerprint.method.addInstruction(
addInstruction( 1,
implementation!!.instructions.lastIndex, "invoke-static/range { p0 .. p0 }, $EXTENSION_CLASS_DESCRIPTOR->activityCreated(Landroid/app/Activity;)V"
"invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->activityCreated(Landroid/app/Activity;)V", )
)
}
offlineVideoEndpointFingerprint.method.apply { offlineVideoEndpointFingerprint.method.apply {
addInstructionsWithLabels( addInstructionsWithLabels(

View File

@@ -20,8 +20,6 @@ val seekbarPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -18,8 +18,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/SeekbarThumbnailsPatch;" "Lapp/revanced/extension/youtube/patches/SeekbarThumbnailsPatch;"
val seekbarThumbnailsPatch = bytecodePatch( val seekbarThumbnailsPatch = bytecodePatch(
description = "Adds an option to use high quality fullscreen seekbar thumbnails. " + description = "Adds an option to use high quality fullscreen seekbar thumbnails."
"Patching 19.16.39 adds an option to restore old seekbar thumbnails.",
) { ) {
dependsOn( dependsOn(
sharedExtensionPatch, sharedExtensionPatch,

View File

@@ -14,7 +14,7 @@ import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_43_or_greater
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.shared.mainActivityFingerprint import app.revanced.patches.youtube.shared.mainActivityConstructorFingerprint
import app.revanced.util.* import app.revanced.util.*
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
@@ -43,10 +43,7 @@ private val swipeControlsResourcePatch = resourcePatch {
SwitchPreference("revanced_swipe_haptic_feedback"), SwitchPreference("revanced_swipe_haptic_feedback"),
SwitchPreference("revanced_swipe_save_and_restore_brightness"), SwitchPreference("revanced_swipe_save_and_restore_brightness"),
SwitchPreference("revanced_swipe_lowest_value_enable_auto_brightness"), SwitchPreference("revanced_swipe_lowest_value_enable_auto_brightness"),
ListPreference( ListPreference("revanced_swipe_overlay_style"),
"revanced_swipe_overlay_style",
summaryKey = null,
),
TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER), TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_overlay_progress_brightness_color", TextPreference("revanced_swipe_overlay_progress_brightness_color",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference", tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
@@ -91,8 +88,6 @@ val swipeControlsPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",
@@ -103,7 +98,7 @@ val swipeControlsPatch = bytecodePatch(
execute { execute {
val wrapperClass = swipeControlsHostActivityFingerprint.classDef val wrapperClass = swipeControlsHostActivityFingerprint.classDef
val targetClass = mainActivityFingerprint.classDef val targetClass = mainActivityConstructorFingerprint.classDef
// Inject the wrapper class from the extension into the class hierarchy of MainActivity. // Inject the wrapper class from the extension into the class hierarchy of MainActivity.
wrapperClass.setSuperClass(targetClass.superclass) wrapperClass.setSuperClass(targetClass.superclass)

View File

@@ -24,8 +24,6 @@ val autoCaptionsPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -43,8 +43,6 @@ val customBrandingPatch = resourcePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -41,8 +41,6 @@ val changeHeaderPatch = resourcePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -22,8 +22,6 @@ val hideButtonsPatch = resourcePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -40,8 +40,6 @@ val navigationButtonsPatch = bytecodePatch(
compatibleWith( compatibleWith(
"com.google.android.youtube"( "com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42", "19.34.42",
"19.43.41", "19.43.41",
"19.47.53", "19.47.53",

View File

@@ -2,6 +2,7 @@ package app.revanced.patches.youtube.layout.buttons.overlay
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.util.containsLiteralInstruction import app.revanced.util.containsLiteralInstruction
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal val playerControlsPreviousNextOverlayTouchFingerprint = fingerprint { internal val playerControlsPreviousNextOverlayTouchFingerprint = fingerprint {
@@ -20,3 +21,10 @@ internal val mediaRouteButtonFingerprint = fingerprint {
methodDef.definingClass.endsWith("/MediaRouteButton;") && methodDef.name == "setVisibility" methodDef.definingClass.endsWith("/MediaRouteButton;") && methodDef.name == "setVisibility"
} }
} }
internal val inflateControlsGroupLayoutStubFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters()
returns("V")
literal { controlsButtonGroupLayoutStub }
}

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