Compare commits

...

391 Commits

Author SHA1 Message Date
oSumAtrIX
a8572bd4c7 get rid of lateinit 2026-01-31 01:09:56 +01:00
oSumAtrIX
48b3a2d18c Merge dev 2026-01-30 20:03:35 +01:00
oSumAtrIX
d39c53f677 youtube music patches work now 2026-01-30 16:28:16 +01:00
oSumAtrIX
2ea26b02ea youtube patches work now 2026-01-30 16:14:35 +01:00
oSumAtrIX
acac6e960c some fixes & update to newest patcher changes 2026-01-29 23:17:33 +01:00
oSumAtrIX
e928128a94 fix error & update to latest patcher 2026-01-28 20:10:53 +01:00
oSumAtrIX
7d891afd2e reflection fix 2026-01-28 15:44:00 +01:00
oSumAtrIX
545fc62b79 use properties without space until android minsdk 29 2026-01-28 15:21:51 +01:00
oSumAtrIX
03c7e353a7 progres 2026-01-28 14:14:10 +01:00
oSumAtrIX
43f919d9ad progress 2026-01-28 13:42:56 +01:00
oSumAtrIX
ac5b65fcb5 some more progress 2026-01-28 13:34:35 +01:00
semantic-release-bot
2c0e81ee17 chore: Release v5.50.0-dev.4 [skip ci]
# [5.50.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.3...v5.50.0-dev.4) (2026-01-27)

### Bug Fixes

* **Instagram - Open links externally:** Fix patch by handling >4-bit register ([#6538](https://github.com/ReVanced/revanced-patches/issues/6538)) ([f681a6f](f681a6ffd4))
2026-01-27 18:47:27 +00:00
xehpuk
f681a6ffd4 fix(Instagram - Open links externally): Fix patch by handling >4-bit register (#6538) 2026-01-27 19:42:25 +01:00
oSumAtrIX
cd23ee4b6c indices replacement and more refactor 2026-01-26 21:57:23 +01:00
oSumAtrIX
bfdc103a93 migrations 2026-01-26 20:31:00 +01:00
semantic-release-bot
59f9e4328f chore: Release v5.50.0-dev.3 [skip ci]
# [5.50.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.2...v5.50.0-dev.3) (2026-01-26)

### Features

* **Kleinanzeigen:** Add `Hide ads` patch ([#6533](https://github.com/ReVanced/revanced-patches/issues/6533)) ([bd6e544](bd6e544007))
2026-01-26 14:24:11 +00:00
xehpuk
bd6e544007 feat(Kleinanzeigen): Add Hide ads patch (#6533)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-26 15:20:59 +01:00
oSumAtrIX
b6975fe015 fixes of errors made by AI 2026-01-26 15:13:01 +01:00
oSumAtrIX
7302d29383 made by AI 2026-01-25 21:39:35 +01:00
oSumAtrIX
bae293e3dd some more migration 2026-01-25 21:03:07 +01:00
oSumAtrIX
1026a8fbd7 original -> immutable 2026-01-25 20:30:19 +01:00
oSumAtrIX
c82691380c remove imports 2026-01-25 20:16:22 +01:00
semantic-release-bot
dbdd2a67ff chore: Release v5.50.0-dev.2 [skip ci]
# [5.50.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.1...v5.50.0-dev.2) (2026-01-25)

### Bug Fixes

* **Letterboxd - Hide ads:** Fix patch by returning the correct return type ([#6527](https://github.com/ReVanced/revanced-patches/issues/6527)) ([80c34b9](80c34b9d74))
2026-01-25 10:08:33 +00:00
xehpuk
80c34b9d74 fix(Letterboxd - Hide ads): Fix patch by returning the correct return type (#6527) 2026-01-25 11:03:06 +01:00
semantic-release-bot
debf297d9e chore: Release v5.50.0-dev.1 [skip ci]
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-25)

### Bug Fixes

* **Strava:** Fix `Add media download` patch ([#6526](https://github.com/ReVanced/revanced-patches/issues/6526)) ([dc9e68b](dc9e68ba57))

### Features

* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](421cb2899e))
* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](66b0852f8f))
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](5edd9dccae))
2026-01-25 00:53:06 +00:00
xehpuk
dc9e68ba57 fix(Strava): Fix Add media download patch (#6526) 2026-01-25 01:49:33 +01:00
oSumAtrIX
3a3fe5ed9b some more migrations 2026-01-25 01:49:03 +01:00
oSumAtrIX
56eff2a625 some more migrations & fixing = to by for creating patches 2026-01-25 01:10:30 +01:00
oSumAtrIX
230ab2fa59 suppress and other migratios 2026-01-24 19:55:54 +01:00
oSumAtrIX
d6a437c7b1 migrate within 2026-01-24 16:45:15 +01:00
oSumAtrIX
99bfecbc97 use invoke opcode 2026-01-24 15:31:29 +01:00
oSumAtrIX
cab788d4ad correct use of endsWith etc 2026-01-24 15:23:03 +01:00
oSumAtrIX
2fac6ccb32 add missing ObjectPropertyName supression 2026-01-24 15:18:07 +01:00
oSumAtrIX
6da8a69f22 instructionMathes -> indices 2026-01-23 23:45:59 +01:00
oSumAtrIX
6afaf53370 remove left over imports 2026-01-23 23:34:21 +01:00
oSumAtrIX
466043132a regex 2026-01-23 23:26:14 +01:00
oSumAtrIX
e192cbae52 parametertypes and returntype 2026-01-23 23:15:14 +01:00
oSumAtrIX
e79b4246a5 Changes by ushie 2026-01-23 23:08:35 +01:00
oSumAtrIX
8c72146bb9 progress 2026-01-23 23:00:57 +01:00
oSumAtrIX
c1a8045ebb Merge remote-tracking branch 'Ushie/refactor/modernize-api-ushie' into fork/Ushie/refactor/modernize-api-ushie
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/amazon/DeepLinkingPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/cieid/restrictions/root/BypassRootChecksPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/duolingo/debug/EnableDebugMenuPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/facebook/ads/story/HideStoryAdsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/finanzonline/detection/bootloader/BootloaderDetectionPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/hexeditor/ad/DisableAdsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/inshorts/ad/InshortsAdsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/lightroom/misc/bypassVersionCheck/DisableVersionCheckPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/music/layout/buttons/HideButtons.kt
#	patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt
#	patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/protonvpn/splittunneling/UnlockSplitTunneling.kt
#	patches/src/main/kotlin/app/revanced/patches/strava/upselling/DisableSubscriptionSuggestionsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/cleardisplay/RememberClearDisplayPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/downloads/DownloadsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/tiktok/interaction/speed/PlaybackSpeedPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/tiktok/misc/login/fixgoogle/FixGoogleLoginPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/tumblr/annoyances/popups/DisableGiftMessagePopupPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/twitch/chat/autoclaim/AutoClaimChannelPointsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/twitter/layout/viewcount/HideViewCountPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/miniplayer/MiniplayerPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/Fingerprints.kt
2026-01-23 18:00:45 +01:00
oSumAtrIX
63a73d0eab some more fixes 2026-01-23 17:54:14 +01:00
Ushie
d0ea3d8d31 refactor: Switch to creatingBytecodePatch 2026-01-23 19:45:50 +03:00
oSumAtrIX
ef3272bf90 properly implement new resource mapping patch apis 2026-01-23 17:24:46 +01:00
oSumAtrIX
4950ac412f some fixes 2026-01-23 17:15:54 +01:00
semantic-release-bot
ebea32fe87 chore: Release v5.50.0-dev.1 [skip ci]
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-22)

### Features

* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](421cb2899e))
* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](66b0852f8f))
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](5edd9dccae))
2026-01-22 19:21:45 +00:00
xehpuk
66b0852f8f feat(Strava): Add Hide distractions patch (#6479)
Co-authored-by: Pun Butrach <pun.butrach@gmail.com>
Co-authored-by: ekaunt <62402760+ekaunt@users.noreply.github.com>
Co-authored-by: bengross <bengross@vecta.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-22 19:56:27 +01:00
Pa1NarK
421cb2899e feat(Nothing X): Add Show K1 token(s) patch (#6490)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-22 19:17:45 +01:00
semantic-release-bot
97e74157fa chore: Release v5.50.0-dev.1 [skip ci]
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.50.0-dev.1) (2026-01-22)

### Features

* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([89645dc](89645dcc2e))
2026-01-22 19:10:39 +01:00
Johannes Obermeier
5edd9dccae feat(YouTube Music): Add Unlock Android Auto Media Browser patch (#6477)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
(cherry picked from commit 89645dcc2e13603b8f2fedb5e16231cb396e5965)
2026-01-22 19:04:09 +01:00
semantic-release-bot
9c18e1e649 chore: Release v5.49.0-dev.1 [skip ci]
# [5.49.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.1-dev.1...v5.49.0-dev.1) (2026-01-22)

### Features

* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](71ce8230a9))
2026-01-22 17:43:08 +00:00
rospino74
71ce8230a9 feat(YouTube Music): Add Hide layout components patch (#6365)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-22 18:36:37 +01:00
github-actions[bot]
156441d3cf chore: Sync translations (#6516)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-01-22 18:21:28 +01:00
oSumAtrIX
634f47ef84 ci: Set necessary env vars for running Gradle task 2026-01-22 17:37:44 +01:00
oSumAtrIX
eeb133325e ci: Properly implement Crowdin strings processing 2026-01-22 17:37:44 +01:00
Ushie
69aa683901 refactor: Modernize APIs 2026-01-22 07:08:20 +03:00
oSumAtrIX
29699557aa some more progress 2026-01-21 21:02:51 +01:00
semantic-release-bot
e8d58ca9af chore: Release v5.48.1-dev.1 [skip ci]
## [5.48.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.48.1-dev.1) (2026-01-21)

### Bug Fixes

* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](5b5c50254d))
2026-01-21 17:49:31 +00:00
Sayanth
5b5c50254d fix: Disable Prevent screenshot detection by default (#6511) 2026-01-21 18:44:04 +01:00
semantic-release-bot
ef052c0d8f chore: Release v5.48.0 [skip ci]
# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19)

### Bug Fixes

* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](eecc44b956))
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](44e7dbcf4d))
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](789f0a5628))
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](71c6cb569e))
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](a429824bb7))
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](87247590de))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](dc69f2433e))

### Features

* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](4cc315952d))
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](83c0127ebb))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](6312fe8d60))
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](6bb6281149))
* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](8725a49ba3))
* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](18c0b04f0c))
* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](3401467a6d))
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](d25dcfe49a))
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](e0f33468e6))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](315931cbf8))
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](4c4ba1c78c))
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](778d13ce8b))
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](c47beae213))
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](f5cbb31724))
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](8f3f4c95bb))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](b42ae27ce6))
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](19f146c01d))
2026-01-19 09:28:58 +00:00
oSumAtrIX
d9fa580222 chore: Merge branch dev to main (#6385) 2026-01-19 10:25:41 +01:00
semantic-release-bot
182224c79d chore: Release v5.48.0-dev.13 [skip ci]
# [5.48.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.12...v5.48.0-dev.13) (2026-01-19)

### Features

* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](83c0127ebb))
2026-01-19 09:21:21 +00:00
xehpuk
83c0127ebb feat: Add Prevent screenshot detection patch (#6482)
Co-authored-by: Swakshan <56347042+Swakshan@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 10:16:30 +01:00
semantic-release-bot
3762f1de08 chore: Release v5.48.0-dev.12 [skip ci]
# [5.48.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.11...v5.48.0-dev.12) (2026-01-19)

### Features

* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](18c0b04f0c))
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](4c4ba1c78c))
2026-01-19 09:06:41 +00:00
Swakshan
18c0b04f0c feat(Instagram): Add Remove build expired popup patch (#6488)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 10:01:42 +01:00
xehpuk
4c4ba1c78c feat(Strava): Add Add 'Give Kudos' button to 'Group Activity' patch (#6475)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 10:00:25 +01:00
semantic-release-bot
7cef24a5e9 chore: Release v5.48.0-dev.11 [skip ci]
# [5.48.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.10...v5.48.0-dev.11) (2026-01-19)

### Features

* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](8725a49ba3))
2026-01-19 08:56:06 +00:00
Swakshan
8725a49ba3 feat(Instagram): Add Hide highlights tray patch (#6489)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 09:52:36 +01:00
semantic-release-bot
8b6360e34f chore: Release v5.48.0-dev.10 [skip ci]
# [5.48.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.9...v5.48.0-dev.10) (2026-01-19)

### Bug Fixes

* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](eecc44b956))
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](87247590de))

### Features

* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](3401467a6d))
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](778d13ce8b))
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](19f146c01d))
2026-01-19 08:40:58 +00:00
Pun Butrach
a10c51f160 ci: Include require environment variables in release step (#6493) 2026-01-19 09:38:25 +01:00
scruz
eecc44b956 fix(Boost for Reddit - Fix missing audio in video downloads): Make it work again by reflecting Reddits latest changes (#6500) 2026-01-18 16:21:59 +01:00
oSumAtrIX
44320b34f6 use new delegate api 2026-01-17 18:45:29 +01:00
oSumAtrIX
62aa4ed555 feat: Modernise APIs (#6476) 2026-01-17 18:28:00 +01:00
oSumAtrIX
e7d2d7d62b some more fixes 2026-01-17 18:27:26 +01:00
oSumAtrIX
3401467a6d feat(Instagram): Disable Disable Reels scrolling by default 2026-01-15 23:30:17 +01:00
oSumAtrIX
5a15476a9f fix names 2026-01-14 02:31:53 +01:00
oSumAtrIX
50f95543f1 fix 2026-01-14 02:23:45 +01:00
Swakshan
87247590de fix(Instagram): Sanitize sharing links (#6483) 2026-01-13 20:10:25 +01:00
xehpuk
41e2590584 chore(Strava): Restructure media package (#6480) 2026-01-13 15:46:49 +01:00
xehpuk
778d13ce8b feat(Strava): Add Add media download patch (#6449)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-12 23:28:15 +01:00
ekaunt
19f146c01d feat(YouTube): Add Pause on audio interrupt patch (#6464)
Co-authored-by: bengross <bengross@vecta.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-12 23:18:09 +01:00
Pun Butrach
8c0c3b44e5 fix(reddit/joeyforreddit): Switch import naming 2026-01-12 17:28:44 +07:00
Pun Butrach
811a7855f1 fix(reddit/syncforlemmy): Switch import naming 2026-01-12 17:28:40 +07:00
Pun Butrach
bce0a745db refactor(photomath): UnlockPlusPatch 2026-01-12 17:26:27 +07:00
Pun Butrach
f4a04e7917 refactor(photomath): EnableBookpointPatch 2026-01-12 17:25:01 +07:00
Pun Butrach
be005c533a refactor(photomath): HideUpdatePopupPatch 2026-01-12 17:22:27 +07:00
Pun Butrach
70328c5a65 fix(photomath/Spoof Device Id): Switch import naming 2026-01-12 17:17:11 +07:00
Pun Butrach
1ee89fe0e6 refactor(photomath): SignatureDetectionPatch 2026-01-12 17:16:28 +07:00
Pun Butrach
530f81b436 refactor(photomath): SpoofDeviceIdPatch 2026-01-12 17:12:08 +07:00
Pun Butrach
198cb93ef2 refactor(primevideo): SkipAdsPatch 2026-01-12 17:10:00 +07:00
Pun Butrach
ad783451ed refactor(primevideo): RenamePermissionsPatch 2026-01-12 17:06:10 +07:00
Pun Butrach
165f4b45e0 refactor(primevideo): PlaybackSpeedPatch 2026-01-12 17:05:23 +07:00
Pun Butrach
fcce0a6948 refactor(syncforreddit): SpoofClientPatch 2026-01-12 17:01:25 +07:00
Pun Butrach
357051105c fix(reddit/sync/Disable Piracy Detection): Privacy typo 2026-01-12 17:01:16 +07:00
Pun Butrach
2e6b7c2fe4 refactor(syncforreddit): DisableSyncForLemmyBottomSheetPatch 2026-01-12 16:49:57 +07:00
Pun Butrach
dd5b9ac58d fix(joeyforreddit/Disable ads): Switch import naming 2026-01-12 16:47:12 +07:00
Pun Butrach
5477e8edbe refactor(sync): DisablePiracyDetectionPatch 2026-01-12 16:46:43 +07:00
Pun Butrach
f4f43660da refactor(reddit): block patch naming 2026-01-12 16:34:39 +07:00
Pun Butrach
60861a8506 refactor(slide): SpoofClientPatch 2026-01-12 16:33:23 +07:00
Pun Butrach
decd7cb23c refactor(relayforreddit): SpoofClientPatch 2026-01-12 16:29:36 +07:00
Pun Butrach
8dfcbd09cc refactor(redditisfun): SpoofClientPatch 2026-01-12 16:27:33 +07:00
Pun Butrach
94cc175b55 refactor(joeyforreddit): DisablePiracyDetectionPatch 2026-01-12 16:08:28 +07:00
Pun Butrach
b6c1ec7d18 refactor(joeyforreddit): SpoofClientPatch 2026-01-12 16:03:25 +07:00
Pun Butrach
cdec438fd4 refactor(joeyforreddit): DisableAdsPatch 2026-01-12 15:59:23 +07:00
Pun Butrach
555b6fe3ca refactor(infinityforreddit): UnlockSubscriptionPatch 2026-01-12 15:57:16 +07:00
Pun Butrach
7787268bed refactor(boostforreddit): FixRedgifsApiPatch 2026-01-12 15:54:53 +07:00
Pun Butrach
24cb9d987b refactor(boostforreddit): FixAudioMissingInDownloadsPatch 2026-01-12 15:52:02 +07:00
Pun Butrach
68a57901d8 refactor(boostforreddit): FixSLinksPatch 2026-01-12 15:47:33 +07:00
Pun Butrach
e2bc428b29 refactor(boostforreddit): SpoofClientPatch 2026-01-12 15:44:53 +07:00
Pun Butrach
968dfde7a8 refactor(boostforreddit): DisableAdsPatch 2026-01-12 15:42:53 +07:00
Pun Butrach
8b833268bb refactor(baconreader): SpoofClientPatch 2026-01-12 15:40:35 +07:00
Pun Butrach
eadc76b161 refactor(baconreader): FixRedgifsApiPatch 2026-01-12 15:28:50 +07:00
Pun Butrach
4540e7a484 refactor(reddit): HideAdsPatch 2026-01-12 15:20:18 +07:00
Pun Butrach
da08df7d9e refactor(reddit): HideCommentAdsPatch 2026-01-12 15:07:36 +07:00
Pun Butrach
f8e912f937 refactor(reddit): HideBannerPatch 2026-01-12 15:04:05 +07:00
Pun Butrach
ae0240c69b refactor(music): VersionCheckPatch 2026-01-12 14:58:21 +07:00
Pun Butrach
7a41f5b285 refactor(music): SettingsPatch 2026-01-12 14:53:46 +07:00
Pun Butrach
12b819d20e ci: Schedule Crowdin to runs weekly instead of every 12 hours (#6466) 2026-01-12 02:40:19 +01:00
Pun Butrach
61225c825c refactor(protonmail): RemoveSentFromSignaturePatch 2026-01-12 00:10:19 +07:00
Pun Butrach
c7ea78824a refactor(protonmail): RemoveFreeAccountsLimitPatch 2026-01-12 00:10:13 +07:00
Pun Butrach
6a4c03220e refactor(protonvpn): RemoveDelayPatch 2026-01-12 00:08:18 +07:00
Pun Butrach
c17bfcb452 refactor(piccomafr): DisableTrackingPatch 2026-01-12 00:04:41 +07:00
Pun Butrach
1cecadf54c refactor(rar): HidePurchaseReminderPatch 2026-01-12 00:01:21 +07:00
Pun Butrach
e7d596e4d8 refactor(pixiv): HideAdsPatch 2026-01-11 23:59:39 +07:00
Pun Butrach
d5c56c4889 refactor(myexpenses): UnlockProPatch 2026-01-11 23:54:35 +07:00
Pun Butrach
8e94a17c28 refactor(myfitnesspal): HideAdsPatch 2026-01-11 23:52:48 +07:00
Pun Butrach
b8cacb507d refactor(netguard): RemoveBroadcastsRestrictionPatch 2026-01-11 23:50:09 +07:00
Pun Butrach
6dfe4440bb refactor(peacocktv): HideAdsPatch 2026-01-11 23:46:19 +07:00
Pun Butrach
043cae5db6 refactor(pandora): EnableUnlimitedSkipsPatch 2026-01-11 23:45:28 +07:00
Pun Butrach
b174421e2b refactor(pandora): DisableAudioAdsPatch 2026-01-11 23:45:21 +07:00
Pun Butrach
b6d1f1c62a refactor(orfon): RemoveRootDetectionPatch 2026-01-11 23:29:48 +07:00
Pun Butrach
52ae070369 refactor(openinghours): FixCrashPatch 2026-01-11 23:19:22 +07:00
Pun Butrach
8fbcf002e4 refactor(nunl): SpoofCertificatePatch 2026-01-11 23:11:02 +07:00
Pun Butrach
678645723a refactor(nunl): HideAdsPatch 2026-01-11 23:10:26 +07:00
Pun Butrach
004b5908db build: Use Gradle credentials system (#6467) 2026-01-11 16:59:48 +01:00
oSumAtrIX
32dfe48ad7 finish batch1 2026-01-11 03:09:35 +01:00
oSumAtrIX
a103eb5b7a migrate batch 2026-01-09 20:00:49 +01:00
oSumAtrIX
69a71fbd3a begin migration 2026-01-08 11:56:03 +01:00
oSumAtrIX
b6cc108fba Merge branch 'dev' into feat/modernize-api
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt
#	patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt
2026-01-08 10:28:06 +01:00
semantic-release-bot
f4af27dfec chore: Release v5.48.0-dev.9 [skip ci]
# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08)

### Features

* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](4cc315952d))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](6312fe8d60))
2026-01-08 00:11:57 +00:00
xehpuk
4cc315952d feat: Add Disable Sentry telemetry patch (#6416) 2026-01-08 01:08:52 +01:00
1fexd
6312fe8d60 feat: Disable Play Integrity patch (#6412)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-08 01:06:05 +01:00
Ushie
3d754575a4 ci: Simplify Crowdin translation file destination path (#6463) 2026-01-07 22:31:15 +01:00
oSumAtrIX
cceb7a7c43 update to new options API 2026-01-06 16:24:54 +01:00
semantic-release-bot
a3f7609fe3 chore: Release v5.48.0-dev.8 [skip ci]
# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04)

### Features

* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](d25dcfe49a))
2026-01-04 13:17:54 +00:00
Swakshan
d25dcfe49a feat(Letterboxd): Add Unlock app icons patch (#6415) 2026-01-04 14:14:29 +01:00
semantic-release-bot
1cc2cb9cb2 chore: Release v5.48.0-dev.7 [skip ci]
# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04)

### Features

* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](f5cbb31724))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](b42ae27ce6))
2026-01-04 02:43:29 +00:00
xehpuk
f5cbb31724 feat(Strava): Add Disable Quick Edit patch (#6452)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-04 03:38:04 +01:00
xehpuk
b42ae27ce6 feat(Strava): Add Overwrite media upload parameters patch (#6410) 2026-01-04 03:36:08 +01:00
semantic-release-bot
43ab29d03d chore: Release v5.48.0-dev.6 [skip ci]
# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04)

### Bug Fixes

* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](789f0a5628))
2026-01-04 02:07:07 +00:00
xehpuk
789f0a5628 fix: Fix build error introduced in 4046bee (#6417) 2026-01-04 03:03:44 +01:00
semantic-release-bot
da836b667c chore: Release v5.48.0-dev.5 [skip ci]
# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30)

### Bug Fixes

* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](44e7dbcf4d))
2025-12-30 18:37:28 +00:00
ILoveOpenSourceApplications
44e7dbcf4d fix(Disney+ - Skip ads): Remove unsupported package names (#6422) 2025-12-30 19:32:17 +01:00
oSumAtrIX
30076b7a9b update to new apply, afterDependents and Patch api 2025-12-30 01:22:15 +01:00
oSumAtrIX
bbd8ae0e24 merge dev 2025-12-30 01:14:20 +01:00
oSumAtrIX
641a23b35a temp 2025-12-30 00:58:32 +01:00
semantic-release-bot
195c239000 chore: Release v5.48.0-dev.4 [skip ci]
# [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29)

### Features

* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](c47beae213))
2025-12-29 21:25:35 +00:00
xehpuk
c47beae213 feat(Strava): Add Block Snowplow tracking patch (#6413)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-29 22:20:26 +01:00
semantic-release-bot
cebcfab86a chore: Release v5.48.0-dev.3 [skip ci]
# [5.48.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.2...v5.48.0-dev.3) (2025-12-28)

### Bug Fixes

* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](71c6cb569e))

### Features

* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](6bb6281149))
2025-12-28 22:33:37 +00:00
xehpuk
71c6cb569e fix: Fix compilation error introduced in 6bb6281 (#6409) 2025-12-28 23:30:32 +01:00
PainfulPaladins
6bb6281149 feat(Instagram - Hides navigation buttons): Add more buttons to hide (#6390) 2025-12-27 18:50:08 +01:00
semantic-release-bot
16bd96e2bb chore: Release v5.48.0-dev.2 [skip ci]
# [5.48.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.1...v5.48.0-dev.2) (2025-12-27)

### Features

* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](8f3f4c95bb))
2025-12-27 17:48:32 +00:00
xehpuk
8f3f4c95bb feat(Strava): Add Enable password login patch (#6396)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-27 18:45:09 +01:00
semantic-release-bot
da02d68587 chore: Release v5.48.0-dev.1 [skip ci]
# [5.48.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0-dev.1) (2025-12-23)

### Bug Fixes

* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](a429824bb7))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](dc69f2433e))

### Features

* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](e0f33468e6))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](315931cbf8))
2025-12-23 01:30:13 +00:00
xehpuk
a429824bb7 fix: Fix compilation error introduced in dc69f243 (#6392) 2025-12-23 02:26:54 +01:00
Sylvain Finot
e0f33468e6 feat(ProtonVPN): Add Unlock split tunneling patch (#6353)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-22 15:00:08 +01:00
trespyian
315931cbf8 feat(SBS On Demand): Add Remove ads patch (#6378)
Co-authored-by: Trespyian <trespyian@nowhere.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-22 14:49:19 +01:00
ILoveOpenSourceApplications
dc69f2433e fix(YouTube - Hide layout components): Hide new type of crowdfunding box (#6380) 2025-12-21 23:10:35 +01:00
semantic-release-bot
73e43b2a49 chore: Release v5.47.0 [skip ci]
# [5.47.0](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0) (2025-12-18)

### Bug Fixes

* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](bb745b555b))
* **Lightroom:** Add `Disable version check` patch to fix opening the app  ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](018d176914))
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](f8bd1239cc))
* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](34830ba63b))
* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](ded8370207))
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](9495cf49ef))
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](654d091e65))

### Features

* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](1f4f252c81))
* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](6bd7dca75b))
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](0ea3491227))
* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](94ae84ad0f))
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](2f0de15e67))
* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](0928dcd00d))
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](0af0ee92c4))
* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](847ee189a9))
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](bbd8932b2e))
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](ac583d40d0))
* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](a5d197b977))
* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](02831a6069))
* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](a7c220a4ae))
2025-12-18 12:14:21 +00:00
oSumAtrIX
918f04793f chore: Merge branch dev to main (#6282) 2025-12-18 13:10:41 +01:00
semantic-release-bot
f1a9537f01 chore: Release v5.47.0-dev.18 [skip ci]
# [5.47.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.17...v5.47.0-dev.18) (2025-12-18)

### Features

* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](1f4f252c81))
2025-12-18 12:09:57 +00:00
vippium
1f4f252c81 feat(Disney+ - SkipAds): Add other package names the patch is compatible with (#6372) 2025-12-18 12:59:47 +01:00
semantic-release-bot
2b560f5fe9 chore: Release v5.47.0-dev.17 [skip ci]
# [5.47.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.16...v5.47.0-dev.17) (2025-12-18)

### Bug Fixes

* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](f8bd1239cc))
2025-12-18 02:05:14 +00:00
g9q
f8bd1239cc fix(Reddit - Hide ads): Update patch for new versions of Reddit (#6342)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-18 03:01:25 +01:00
semantic-release-bot
c825ebda37 chore: Release v5.47.0-dev.16 [skip ci]
# [5.47.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.15...v5.47.0-dev.16) (2025-12-15)

### Bug Fixes

* **Lightroom:** Add `Disable version check` patch to fix opening the app  ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](018d176914))

### Features

* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](0ea3491227))
2025-12-15 11:34:05 +00:00
oSumAtrIX
255c00b183 chore: Fix minor syntax error 2025-12-15 12:28:53 +01:00
Alex Katlein
0ea3491227 feat(IdAustria - Remove device integrity check): Update patch to work with latest version (#6360)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-15 12:26:59 +01:00
Pun Butrach
5d437b08dd docs: Use American spelling (#6233) 2025-12-14 16:38:55 +01:00
f1re4xx
018d176914 fix(Lightroom): Add Disable version check patch to fix opening the app (#6315)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-14 16:38:26 +01:00
semantic-release-bot
9a77beea8a chore: Release v5.47.0-dev.15 [skip ci]
# [5.47.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.14...v5.47.0-dev.15) (2025-12-13)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](ded8370207))
2025-12-13 20:26:05 +00:00
ILoveOpenSourceApplications
ded8370207 fix(YouTube - Hide layout components): Fix "Hide Subscribe button" in channel page not working (#6363) 2025-12-13 21:22:35 +01:00
semantic-release-bot
4d1104fc32 chore: Release v5.47.0-dev.14 [skip ci]
# [5.47.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.13...v5.47.0-dev.14) (2025-12-13)

### Bug Fixes

* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](34830ba63b))
2025-12-13 08:52:03 +00:00
Cilly Leang
34830ba63b fix(Spotify): Make patches work with latest versions again (#6359) 2025-12-13 09:48:39 +01:00
github-actions[bot]
7a6894d809 chore: Sync translations (#6344)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-12-10 03:58:45 +01:00
semantic-release-bot
144e6e2694 chore: Release v5.47.0-dev.13 [skip ci]
# [5.47.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.12...v5.47.0-dev.13) (2025-12-10)

### Features

* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](847ee189a9))
2025-12-10 02:58:06 +00:00
g9q
847ee189a9 feat(Peacock TV): Add Hide ads patch (#6348) 2025-12-10 03:55:08 +01:00
semantic-release-bot
dc813fe617 chore: Release v5.47.0-dev.12 [skip ci]
# [5.47.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.11...v5.47.0-dev.12) (2025-12-08)

### Features

* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](02831a6069))
2025-12-08 21:14:39 +00:00
ILoveOpenSourceApplications
02831a6069 feat(YouTube - Hide layout components): Add "Hide Join button" and "Hide Subscribe button" options for channel page (#6345) 2025-12-08 22:10:35 +01:00
semantic-release-bot
5228fd4b58 chore: Release v5.47.0-dev.11 [skip ci]
# [5.47.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.10...v5.47.0-dev.11) (2025-12-08)

### Features

* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](6bd7dca75b))
2025-12-08 13:51:15 +00:00
g9q
6bd7dca75b feat(Disney+): Add Skip ads patch (#6343)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-08 14:45:41 +01:00
semantic-release-bot
22ed7bfbb3 chore: Release v5.47.0-dev.10 [skip ci]
# [5.47.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.9...v5.47.0-dev.10) (2025-12-08)

### Features

* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](a7c220a4ae))
2025-12-08 12:57:23 +00:00
ILoveOpenSourceApplications
a7c220a4ae feat(YouTube - Hide Shorts components): Add "Hide auto-dubbed label" and "Hide live preview" options (#6334) 2025-12-08 13:51:57 +01:00
semantic-release-bot
d8ca4ee931 chore: Release v5.47.0-dev.9 [skip ci]
# [5.47.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.8...v5.47.0-dev.9) (2025-12-08)

### Features

* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](a5d197b977))
2025-12-08 12:06:03 +00:00
ILoveOpenSourceApplications
a5d197b977 feat(YouTube - Hide layout components): Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options (#6335)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-08 13:02:22 +01:00
semantic-release-bot
a0ec4c07f7 chore: Release v5.47.0-dev.8 [skip ci]
# [5.47.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.7...v5.47.0-dev.8) (2025-12-08)

### Features

* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](0928dcd00d))
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](bbd8932b2e))
2025-12-08 11:36:43 +00:00
Alexey Gorbachev
0928dcd00d feat(Instagram): Add Disable Reels scrolling patch (#6317)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-08 12:31:54 +01:00
Sylvain Finot
bbd8932b2e feat(ProtonVPN): Add Remove delay patch (#6326)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-12-08 12:30:47 +01:00
semantic-release-bot
300b12f948 chore: Release v5.47.0-dev.7 [skip ci]
# [5.47.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.6...v5.47.0-dev.7) (2025-12-03)

### Features

* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](ac583d40d0))
2025-12-03 15:05:05 +00:00
rospino74
ac583d40d0 feat(Spoof SIM provider): Spoof additional TelephonyManager methods (#6293) 2025-12-03 16:01:08 +01:00
oSumAtrIX
e1e03e1b69 fix: Everything works now 2025-11-27 00:36:35 +01:00
oSumAtrIX
8c603802f7 fix: Adjusting to new API 2025-11-26 23:13:22 +01:00
semantic-release-bot
c400188c38 chore: Release v5.47.0-dev.6 [skip ci]
# [5.47.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.5...v5.47.0-dev.6) (2025-11-24)

### Features

* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](0af0ee92c4))
2025-11-24 12:01:20 +00:00
Swakshan
0af0ee92c4 feat(Letterboxd): Add Hide ads patch (#6309)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-11-24 12:56:55 +01:00
oSumAtrIX
95c72ad300 feat: Use new API 2025-11-22 23:23:57 +01:00
LisoUseInAIKyrios
2f3ecab0e1 drop 20.13.41 for simplicity 2025-11-19 17:06:09 +02:00
LisoUseInAIKyrios
a5d39c3bbe fix splash screen changes not working and remove obsolete debugging code (issue was caused by bad merge of main branch) 2025-11-19 16:54:31 +02:00
LisoUseInAIKyrios
07c4dd3a55 Bump up minimum time after clean launch before bold icons can be forced on 2025-11-19 14:58:33 +02:00
LisoUseInAIKyrios
67c6c345ea Use bold icons by default with YT 20.31+ 2025-11-19 14:48:02 +02:00
LisoUseInAIKyrios
ed514d9755 Move patch warnings to individual patches if user turns off version constrains and patches anyway 2025-11-19 13:07:54 +02:00
LisoUseInAIKyrios
0e994f5bfe Add 20.21.37 to all patches, and 20.31.40 to patches that are known to completely work. 2025-11-19 12:26:20 +02:00
LisoUseInAIKyrios
8cc69fe38b Removing dev code that snuck in 2025-11-17 16:40:45 +02:00
LisoUseInAIKyrios
41b31dd56c refactor 2025-11-17 14:47:06 +02:00
LisoUseInAIKyrios
9671c7499d refactor 2025-11-16 10:25:24 +02:00
LisoUseInAIKyrios
d62d17fdeb unofficial 20.46.33 2025-11-14 08:51:20 +02:00
LisoUseInAIKyrios
c6eaba9af6 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java
2025-11-14 08:19:40 +02:00
semantic-release-bot
fff29544b9 chore: Release v5.47.0-dev.5 [skip ci]
# [5.47.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.4...v5.47.0-dev.5) (2025-11-13)

### Bug Fixes

* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](9495cf49ef))
2025-11-13 07:44:09 +00:00
LisoUseInAIKyrios
9495cf49ef fix(YouTube - Hide player flyout menu items): Allow hiding audio menu with 'Android No SDK' client type 2025-11-13 09:40:28 +02:00
semantic-release-bot
15675b5164 chore: Release v5.47.0-dev.4 [skip ci]
# [5.47.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.3...v5.47.0-dev.4) (2025-11-12)

### Bug Fixes

* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](654d091e65))
2025-11-12 19:01:00 +00:00
LisoUseInAIKyrios
654d091e65 fix(YouTube - Sanitize sharing links): Handle non hierarchical urls 2025-11-12 20:55:32 +02:00
semantic-release-bot
98371be33c chore: Release v5.47.0-dev.3 [skip ci]
# [5.47.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.2...v5.47.0-dev.3) (2025-11-12)

### Features

* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](2f0de15e67))
2025-11-12 07:46:04 +00:00
brosssh
2f0de15e67 feat(Instagram): Add Disable auto story flipping patch (#6262)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-11-12 08:41:15 +01:00
semantic-release-bot
df160370e2 chore: Release v5.47.0-dev.2 [skip ci]
# [5.47.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.1...v5.47.0-dev.2) (2025-11-12)

### Bug Fixes

* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](bb745b555b))
2025-11-12 06:19:34 +00:00
LisoUseInAIKyrios
bb745b555b fix(Instagram - Disable signature check): Change patch to default excluded (#6283) 2025-11-12 08:14:16 +02:00
semantic-release-bot
8df9a46721 chore: Release v5.47.0-dev.1 [skip ci]
# [5.47.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0-dev.1) (2025-11-12)

### Features

* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](94ae84ad0f))
2025-11-12 05:32:16 +00:00
brosssh
94ae84ad0f feat(Instagram): Add Anonymous story viewing patch (#6263) 2025-11-12 07:29:13 +02:00
github-actions[bot]
4febb2e2e9 chore: Sync translations (#6280) 2025-11-12 07:28:43 +02:00
semantic-release-bot
b9bc7e3e58 chore: Release v5.46.0 [skip ci]
# [5.46.0](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0) (2025-11-10)

### Bug Fixes

* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](f238ae9895))
* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](e030e9c07a))
* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](59d85b28a7))
* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](57263538c7))
* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](582144026d))
* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](d390b54dab))
* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](76dcfaefd8))

### Features

* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](2e9d6959c9))
* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](a52c0153b1))
* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](da4cf94091))
* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](858edbf3e7))
* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](ab808aeb77))
* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](7a18ebc7ab))
2025-11-10 10:06:40 +00:00
LisoUseInAIKyrios
9f3bb26cb9 chore: Merge branch dev to main (#6237) 2025-11-10 12:03:02 +02:00
github-actions[bot]
d64dfc2884 chore: Sync translations (#6276) 2025-11-10 12:00:41 +02:00
LisoUseInAIKyrios
db5b79ddbb Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/music/layout/branding/CustomBrandingPatch.kt
2025-11-10 11:24:33 +02:00
LisoUseInAIKyrios
a39ef1e0a4 refactor(YouTube Music - Custom branding): Resolve startup app crash when patching unsupported newer app versions 2025-11-10 11:23:09 +02:00
LisoUseInAIKyrios
a1a80ebc57 fix search results patches 2025-11-10 10:43:08 +02:00
LisoUseInAIKyrios
5ccfb3cb9f Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-11-10 10:41:59 +02:00
semantic-release-bot
1d8e977a43 chore: Release v5.46.0-dev.10 [skip ci]
# [5.46.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.9...v5.46.0-dev.10) (2025-11-09)

### Features

* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](da4cf94091))
2025-11-09 15:35:27 +00:00
ILoveOpenSourceApplications
da4cf94091 feat(YouTube - Hide layout components): Add video description "Hide Featured content" and "Hide Subscribe button" (#6253) 2025-11-09 17:30:07 +02:00
github-actions[bot]
d23fa5e3b7 chore: Sync translations (#6270) 2025-11-09 17:29:11 +02:00
LisoUseInAIKyrios
2687b3006b fix loop video not working 2025-11-09 10:16:34 +02:00
LisoUseInAIKyrios
29a86fb8ec refactor 2025-11-09 09:50:44 +02:00
semantic-release-bot
34d29abdfa chore: Release v5.46.0-dev.9 [skip ci]
# [5.46.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.8...v5.46.0-dev.9) (2025-11-09)

### Features

* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](ab808aeb77))
2025-11-09 07:43:26 +00:00
MarcaD
ab808aeb77 feat(YouTube Music): Add Change miniplayer color patch (#6259)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-11-09 09:39:51 +02:00
github-actions[bot]
a6b07cceb1 chore: Sync translations (#6266) 2025-11-09 09:39:32 +02:00
LisoUseInAIKyrios
81a429af74 delete deprecated binary compatibility 2025-11-09 09:13:58 +02:00
LisoUseInAIKyrios
3406033732 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java
#	patches/src/main/kotlin/app/revanced/patches/music/layout/castbutton/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/music/layout/castbutton/HideCastButton.kt
2025-11-09 09:11:01 +02:00
semantic-release-bot
d291881215 chore: Release v5.46.0-dev.8 [skip ci]
# [5.46.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.7...v5.46.0-dev.8) (2025-11-09)

### Features

* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](7a18ebc7ab))
2025-11-09 07:09:07 +00:00
MarcaD
7a18ebc7ab feat(YouTube Music): Add Hide buttons patch (#6255) 2025-11-09 09:05:31 +02:00
LisoUseInAIKyrios
afbcf3d90f fix exit fullscreen patch 2025-11-08 22:06:54 +02:00
LisoUseInAIKyrios
b7c995930a fix custom speeds over 2.0/4.0 not working 2025-11-08 19:47:40 +02:00
LisoUseInAIKyrios
675a2c4209 add fix content provider patch 2025-11-08 18:34:00 +02:00
semantic-release-bot
475197af45 chore: Release v5.46.0-dev.7 [skip ci]
# [5.46.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.6...v5.46.0-dev.7) (2025-11-08)

### Bug Fixes

* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](d390b54dab))
2025-11-08 12:32:08 +00:00
LisoUseInAIKyrios
0855290097 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-11-08 14:29:22 +02:00
LisoUseInAIKyrios
d390b54dab fix(YouTube - Settings): Add additional languages to ReVanced language preference 2025-11-08 14:28:52 +02:00
github-actions[bot]
4d1eaa6b14 chore: Sync translations (#6260) 2025-11-08 14:26:20 +02:00
LisoUseInAIKyrios
49c925e95f remove 'by' syntax for fingerprints 2025-11-08 10:43:01 +02:00
LisoUseInAIKyrios
7499f3d19b add maven local 2025-11-08 10:35:56 +02:00
LisoUseInAIKyrios
3d55083dbc Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-11-08 10:08:45 +02:00
LisoUseInAIKyrios
c6364f5b49 chore: Fix compilation warning 2025-11-08 10:08:25 +02:00
semantic-release-bot
f177eae385 chore: Release v5.46.0-dev.6 [skip ci]
# [5.46.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.5...v5.46.0-dev.6) (2025-11-08)

### Features

* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](2e9d6959c9))
2025-11-08 08:07:41 +00:00
LisoUseInAIKyrios
829bfa76d1 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ToolbarPreferenceFragment.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/NewSegmentLayout.java
#	patches/src/main/kotlin/app/revanced/patches/spotify/layout/hide/createbutton/Fingerprints.kt
2025-11-08 10:06:22 +02:00
MarcaD
2e9d6959c9 feat(YouTube - Debugging): Add setting to block experimental client flags (#6196) 2025-11-08 10:01:46 +02:00
semantic-release-bot
81f83690d6 chore: Release v5.46.0-dev.5 [skip ci]
# [5.46.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.4...v5.46.0-dev.5) (2025-11-07)

### Bug Fixes

* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](f238ae9895))
* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](e030e9c07a))
* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](59d85b28a7))
2025-11-07 11:39:00 +00:00
LisoUseInAIKyrios
f1bd6848c9 chore(deps-dev): Revert bump semantic-release from 24.2.9 to 25.0.1 (#6204)
This reverts commit 55e1a6784b.
2025-11-07 13:36:18 +02:00
LisoUseInAIKyrios
59d85b28a7 fix(Spotify - Hide Create button): Remove obsolete patch that is no longer needed (#6252) 2025-11-07 13:29:34 +02:00
LisoUseInAIKyrios
f238ae9895 fix(Duolingo - Disable ads): Constrain patch to last working app target 2025-11-07 13:27:31 +02:00
LisoUseInAIKyrios
e030e9c07a fix(Instagram - Hide navigation buttons): Constrain patch to last working app target 2025-11-07 13:25:24 +02:00
dependabot[bot]
5029e979be chore(deps): Bump actions/upload-artifact from 4 to 5 (#6201) 2025-11-07 09:34:24 +02:00
dependabot[bot]
55e1a6784b chore(deps-dev): Bump semantic-release from 24.2.9 to 25.0.1 (#6204) 2025-11-07 09:33:23 +02:00
dependabot[bot]
0cad5e73f0 chore(deps): Bump actions/setup-node from 5 to 6 (#6200) 2025-11-07 09:33:05 +02:00
semantic-release-bot
ce503d5b58 chore: Release v5.46.0-dev.4 [skip ci]
# [5.46.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.3...v5.46.0-dev.4) (2025-11-07)

### Bug Fixes

* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](57263538c7))
2025-11-07 07:17:59 +00:00
LisoUseInAIKyrios
57263538c7 fix(YouTube - Check watch history domain name resolution): Fix false positive warning message if the internet connection fails halfway into the DNS check 2025-11-07 09:14:21 +02:00
LisoUseInAIKyrios
88352d8774 Unofficial 20.45.32 2025-11-07 09:00:28 +02:00
LisoUseInAIKyrios
03ce5711de Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-11-07 08:29:54 +02:00
semantic-release-bot
70f4955e89 chore: Release v5.46.0-dev.3 [skip ci]
# [5.46.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.2...v5.46.0-dev.3) (2025-11-06)

### Bug Fixes

* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](582144026d))
2025-11-06 12:12:38 +00:00
ILoveOpenSourceApplications
582144026d fix(YouTube - Hide layout components): Fix "Hide Hype points" (#6247) 2025-11-06 14:09:43 +02:00
github-actions[bot]
d80892cc0e chore: Sync translations (#6248) 2025-11-06 14:09:25 +02:00
semantic-release-bot
6c4b931b8a chore: Release v5.46.0-dev.2 [skip ci]
# [5.46.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.1...v5.46.0-dev.2) (2025-11-04)

### Bug Fixes

* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](76dcfaefd8))
2025-11-04 13:03:33 +00:00
MarcaD
76dcfaefd8 fix(YouTube - Settings): Resolve settings search crash when searching for specific words (#6231) 2025-11-04 14:59:43 +02:00
github-actions[bot]
e4f52343c0 chore: Sync translations (#6239) 2025-11-04 14:59:20 +02:00
semantic-release-bot
1196b1a147 chore: Release v5.46.0-dev.1 [skip ci]
# [5.46.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0-dev.1) (2025-11-04)

### Features

* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](a52c0153b1))
* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](858edbf3e7))
2025-11-04 07:10:24 +00:00
ILoveOpenSourceApplications
858edbf3e7 feat(YouTube - Hide player flyout menu items): Add "Hide Listen with YouTube Music" (#6232) 2025-11-04 09:07:11 +02:00
ILoveOpenSourceApplications
a52c0153b1 feat(YouTube - Hide layout components): Add "Hide Hype points" (#6230) 2025-11-04 09:05:28 +02:00
LisoUseInAIKyrios
fc70f852f9 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-11-02 09:46:22 +01:00
semantic-release-bot
cd9ef81354 chore: Release v5.45.0 [skip ci]
# [5.45.0](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0) (2025-11-01)

### Bug Fixes

* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](c73a03c9e1))
* **TikTok - Downloads:** Fix download path setting  ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](3e4990afff))
* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](a0c5604951))
* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](6d01863ec7))
* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](292fae440c))
* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](13cf1724bf))

### Features

* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](5f23bfe833))
* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](ef44eaa119))
* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](e9f45ce926))
2025-11-01 15:36:32 +00:00
LisoUseInAIKyrios
1b2cd64a86 chore: Merge branch dev to main (#6174) 2025-11-01 16:32:23 +01:00
semantic-release-bot
0c03599f07 chore: Release v5.45.0-dev.6 [skip ci]
# [5.45.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.5...v5.45.0-dev.6) (2025-11-01)

### Features

* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](5f23bfe833))
2025-11-01 08:25:19 +00:00
LisoUseInAIKyrios
5f23bfe833 feat(Spoof video streams): Add experimental "Android No SDK" client type 2025-11-01 09:19:49 +01:00
github-actions[bot]
2cf8f0e636 chore: Sync translations (#6197) 2025-11-01 09:17:01 +01:00
semantic-release-bot
c17cf98c7e chore: Release v5.45.0-dev.5 [skip ci]
# [5.45.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.4...v5.45.0-dev.5) (2025-11-01)

### Bug Fixes

* **TikTok - Downloads:** Fix download path setting  ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](3e4990afff))
* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](292fae440c))
2025-11-01 07:36:49 +00:00
hxreborn
3e4990afff fix(TikTok - Downloads): Fix download path setting (#6191) 2025-11-01 08:32:26 +01:00
LisoUseInAIKyrios
292fae440c fix(YouTube - Spoof video streams): Remove spoof stream audio selector that no longer works 2025-11-01 08:31:17 +01:00
LisoUseInAIKyrios
955f7c9341 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-10-30 12:27:44 +01:00
LisoUseInAIKyrios
93160722c0 add temporary debug logging 2025-10-30 12:27:37 +01:00
semantic-release-bot
12e7c0943a chore: Release v5.45.0-dev.4 [skip ci]
# [5.45.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.3...v5.45.0-dev.4) (2025-10-30)

### Bug Fixes

* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](a0c5604951))
2025-10-30 08:32:28 +00:00
LisoUseInAIKyrios
78689fde83 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/header/ChangeHeaderPatch.kt
2025-10-30 09:28:16 +01:00
LisoUseInAIKyrios
a0c5604951 fix(YouTube - Change header): Do not mirror header graphic with RTL languages 2025-10-30 09:27:27 +01:00
LisoUseInAIKyrios
38d9299dfe chore: Add branding license text file (#6179) 2025-10-30 09:26:18 +01:00
LisoUseInAIKyrios
260afefaab fix data migration 2025-10-28 12:57:55 +04:00
LisoUseInAIKyrios
b7be52dec6 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/ChangeHeaderPatch.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java
2025-10-27 21:15:19 +04:00
semantic-release-bot
dfdbbfa047 chore: Release v5.45.0-dev.3 [skip ci]
# [5.45.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.2...v5.45.0-dev.3) (2025-10-27)

### Features

* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](e9f45ce926))
2025-10-27 17:14:52 +00:00
MarcaD
e9f45ce926 feat(YouTube - Change Header): Use SVG for header logo (#6178)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-10-27 21:10:49 +04:00
LisoUseInAIKyrios
594317e573 Change lowest supported version to 19.43.41 2025-10-27 11:18:12 +04:00
LisoUseInAIKyrios
c95170ecbd fix fullscreen button vertical alignment 2025-10-27 11:11:51 +04:00
LisoUseInAIKyrios
ecda492866 finish merge 2025-10-27 11:09:55 +04:00
LisoUseInAIKyrios
4a73671262 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/duolingo/debug/EnableDebugMenuPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/duolingo/debug/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/instagram/hide/navigation/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/music/layout/compactheader/HideCategoryBar.kt
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/audio/Fingerprints.kt
2025-10-27 11:06:09 +04:00
semantic-release-bot
1b38b1a3c8 chore: Release v5.45.0-dev.2 [skip ci]
# [5.45.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.1...v5.45.0-dev.2) (2025-10-26)

### Bug Fixes

* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](6d01863ec7))
* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](13cf1724bf))
2025-10-26 15:27:23 +00:00
MarcaD
13cf1724bf fix(YouTube Music - Hide category bar): Correctly hide the category bar in newer app targets (#6175) 2025-10-26 19:23:40 +04:00
LisoUseInAIKyrios
6d01863ec7 fix(YouTube - Force original audio): Fall back to visionOS and not Android Studio if Android VR is not available 2025-10-26 19:23:04 +04:00
semantic-release-bot
a32ed30b4c chore: Release v5.45.0-dev.1 [skip ci]
# [5.45.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0-dev.1) (2025-10-26)

### Bug Fixes

* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](c73a03c9e1))

### Features

* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](ef44eaa119))
2025-10-26 10:05:58 +00:00
hxreborn
ef44eaa119 feat(TikTok): Add Sanitize sharing links patch (#6176) 2025-10-26 14:01:32 +04:00
rospino74
c73a03c9e1 fix(Instagram): Update failing fingerprints on newer versions (#6181) 2025-10-26 11:01:06 +01:00
LisoUseInAIKyrios
c1681f982a chore: Dump api 2025-10-25 08:11:57 +04:00
semantic-release-bot
1502bf7524 chore: Release v5.44.0 [skip ci]
# [5.44.0](https://github.com/ReVanced/revanced-patches/compare/v5.43.1...v5.44.0) (2025-10-24)

### Bug Fixes

* **Google Photos - Spoof features:** Add support for Pixel 10 devices ([#6161](https://github.com/ReVanced/revanced-patches/issues/6161)) ([754b719](754b71959a))
* **X / Twitter - Change link sharing domain:** Use bytecode patching to resolve patching with Manager ([#6125](https://github.com/ReVanced/revanced-patches/issues/6125)) ([0af8c8a](0af8c8a766))
* **YouTube - Hide layout components:** Hide new kind of community post ([#6146](https://github.com/ReVanced/revanced-patches/issues/6146)) ([cfd244b](cfd244b408))
* **YouTube Music:** Resolve patching 7.29 target ([2e4c6fd](2e4c6fdcad))

### Features

* Add `Custom network security` patch ([#6151](https://github.com/ReVanced/revanced-patches/issues/6151)) ([e7336d2](e7336d2ef3))
* **Duolingo - Enable debug menu:** Support latest app target ([#6163](https://github.com/ReVanced/revanced-patches/issues/6163)) ([08baa19](08baa19b4a))
* **Duolingo:** Add `Skip energy recharge ads` patch ([#6167](https://github.com/ReVanced/revanced-patches/issues/6167)) ([591e106](591e106098))
* **Samsung Radio:** Add `Disable device checks` patch ([#6145](https://github.com/ReVanced/revanced-patches/issues/6145)) ([de97562](de97562c5d))
2025-10-24 12:03:24 +00:00
LisoUseInAIKyrios
dffb1e6525 chore: Merge branch dev to main (#6135) 2025-10-24 15:59:21 +04:00
LisoUseInAIKyrios
6d72b4a3fb unofficial 20.43.32 2025-10-23 11:37:45 +04:00
LisoUseInAIKyrios
f00c0e0d89 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/theme/SeekbarColorPatch.java
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatch.kt
2025-10-22 11:52:22 +04:00
LisoUseInAIKyrios
772620c8ce refactor 2025-10-21 21:41:02 +04:00
LisoUseInAIKyrios
9984e586b4 Show bold search icon 2025-10-21 11:58:14 +04:00
LisoUseInAIKyrios
9a2f23291d Don't allow bold icons if spoofing to very old targets 2025-10-21 11:42:25 +04:00
LisoUseInAIKyrios
7ef70f7823 remove bold alert icon 2025-10-21 11:07:21 +04:00
LisoUseInAIKyrios
a1fd6b13d5 reformat xml. no functional changes 2025-10-21 11:03:54 +04:00
LisoUseInAIKyrios
f30ece9287 Add SB bold icon placeholder code 2025-10-20 23:58:09 +04:00
LisoUseInAIKyrios
f2356a8be2 fix swipe bold icon 2025-10-20 23:22:22 +04:00
LisoUseInAIKyrios
47f1a5f9c9 add the last bold resources 2025-10-20 22:18:05 +04:00
LisoUseInAIKyrios
fc988fa078 fix setting availability 2025-10-20 21:57:08 +04:00
LisoUseInAIKyrios
c37527f182 Add ReVanced bold icons 2025-10-20 21:55:14 +04:00
LisoUseInAIKyrios
64334b4f79 refactor 2025-10-20 20:07:17 +04:00
LisoUseInAIKyrios
0389073600 fix minimal miniplayer using incorrectly sized bold icons 2025-10-20 11:42:20 +04:00
LisoUseInAIKyrios
5449357f7f refactor 2025-10-20 11:14:32 +04:00
LisoUseInAIKyrios
39da47e6ee Use YT notification icon that isn't associated with the navigation bar enum (YT still failed to fixed even after a second icon redesign) 2025-10-20 10:58:11 +04:00
LisoUseInAIKyrios
1356a7e5b2 add 20.31+ bold icons setting 2025-10-20 10:35:49 +04:00
LisoUseInAIKyrios
b5cda51048 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/ChangeLinkSharingDomainPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/Fingerprints.kt
2025-10-18 13:22:16 +04:00
LisoUseInAIKyrios
a1ad5fea20 unofficial 20.42.36 2025-10-18 13:21:43 +04:00
LisoUseInAIKyrios
c98c73b0bc Don't turn off new Shorts player flag on newer targets since it can break the Shorts player overlay 2025-10-15 11:46:11 +04:00
LisoUseInAIKyrios
ed87bc7b7a Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/playservice/VersionCheckPatch.kt
2025-10-14 16:18:30 +04:00
LisoUseInAIKyrios
1901e965e8 unofficial 20.41.33 2025-10-14 10:50:32 +04:00
LisoUseInAIKyrios
aae71e6a19 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/instagram/hide/explore/Fingerprints.kt
2025-10-14 09:57:28 +04:00
LisoUseInAIKyrios
a1c9170cc9 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/ChangeLinkSharingDomainPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/Fingerprints.kt
2025-10-11 02:17:07 +04:00
LisoUseInAIKyrios
88b077a9b7 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/gms/GmsCoreSupportPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/ChangeLinkSharingDomainPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt
2025-10-07 23:51:32 +04:00
LisoUseInAIKyrios
563f586eed fix 20.22+ litho broken for some users 2025-10-03 11:15:49 +04:00
LisoUseInAIKyrios
c6becb4044 unofficial 20.40.33 2025-10-03 11:07:34 +04:00
LisoUseInAIKyrios
2d207fccbc Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java
2025-10-03 10:55:41 +04:00
LisoUseInAIKyrios
967ef47c2d Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/navigation/NavigationBarHookPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/video/audio/ForceOriginalAudioPatch.kt
2025-10-01 19:01:29 +04:00
LisoUseInAIKyrios
240e953160 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-10-01 19:00:06 +04:00
LisoUseInAIKyrios
0f03a071e9 use navigation notification button SVG 2025-10-01 17:56:30 +04:00
LisoUseInAIKyrios
e52b33981c Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/ChangeLinkSharingDomainPatch.kt
2025-10-01 11:58:47 +04:00
LisoUseInAIKyrios
4d9de1a81a Use custom navigation bar notification filled icon (20.39 still needs a bytecode fix) 2025-10-01 11:57:53 +04:00
LisoUseInAIKyrios
636bded69c refactor: Add main activity onCreate extension hook function 2025-09-30 22:21:35 +04:00
LisoUseInAIKyrios
2d49d76e82 finish merge 2025-09-28 16:39:28 +04:00
LisoUseInAIKyrios
934947a257 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/api/patches.api
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/branding/CustomBrandingPatch.kt
2025-09-28 16:34:51 +04:00
LisoUseInAIKyrios
8e05bb3a80 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/music/layout/castbutton/HideCastButton.kt
2025-09-27 16:31:53 +04:00
LisoUseInAIKyrios
f88ad4e4a7 modernize 2025-09-27 16:24:28 +04:00
LisoUseInAIKyrios
f252fb24b6 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-27 16:22:00 +04:00
LisoUseInAIKyrios
2f9081eb6c Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-27 16:11:54 +04:00
LisoUseInAIKyrios
a82f49aa08 fix typo 2025-09-26 19:06:51 +04:00
LisoUseInAIKyrios
823530f707 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-26 19:05:20 +04:00
LisoUseInAIKyrios
7eb78d4f2b finish merge 2025-09-26 12:31:58 +04:00
LisoUseInAIKyrios
4dec67385e Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java
#	patches/api/patches.api
#	patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/NavigationBarPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/SettingsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/SeekbarColorPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/LithoColorHookPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/ThemePatch.kt
2025-09-26 12:27:58 +04:00
LisoUseInAIKyrios
edaad1a7b7 remove 20.07 (one of the more difficult and constantly changing sanitize url fingerprints no longer matches, and don't want to fix. Only the oldest and latest are what anyone cares about) 2025-09-25 22:17:01 +04:00
LisoUseInAIKyrios
d3df24977a work in progress cairo notification tab selected icon fix. Icon modified from free icon at https://fontawesome.com/icons/bell?f=classic&s=solid
No attribution required, but png metadata contains the source url
2025-09-25 22:16:37 +04:00
LisoUseInAIKyrios
7b02a31e3f unofficial 20.39 work in progress (navigation bar notification tab icon fix is TODO) 2025-09-25 22:13:12 +04:00
LisoUseInAIKyrios
41c8fbc10d Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-25 21:04:34 +04:00
LisoUseInAIKyrios
59e1321e62 debugging 2025-09-25 16:25:28 +04:00
LisoUseInAIKyrios
252f57f430 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java
2025-09-25 00:33:16 +04:00
LisoUseInAIKyrios
d6593e2acd finish merge 2025-09-23 22:10:14 +04:00
LisoUseInAIKyrios
c4e6e62e71 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/instagram/hide/navigation/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/music/misc/extension/hooks/ApplicationInitHook.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt
2025-09-23 22:07:21 +04:00
LisoUseInAIKyrios
ca736094e4 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-23 10:21:04 +04:00
LisoUseInAIKyrios
7e010d38cc delete deprecated dummy files 2025-09-22 22:05:54 +04:00
LisoUseInAIKyrios
a4d24ad192 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/settings/SettingsPatch.kt
2025-09-22 22:02:40 +04:00
LisoUseInAIKyrios
45d42a1405 fix merge error 2025-09-22 18:48:44 +04:00
LisoUseInAIKyrios
ecf5752100 fix merge error 2025-09-22 17:39:11 +04:00
LisoUseInAIKyrios
4096b34003 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt
2025-09-22 15:05:43 +04:00
LisoUseInAIKyrios
23b200ce68 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-22 15:03:55 +04:00
LisoUseInAIKyrios
56876f336b finish merge 2025-09-21 21:21:56 +04:00
LisoUseInAIKyrios
c7a71f44df Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/RemoveTrackingQueryParameterPatch.java
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/privacy/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/privacy/RemoveTrackingQueryParameterPatch.kt
2025-09-21 21:17:44 +04:00
LisoUseInAIKyrios
a25d769f69 remove deprecated migration code 2025-09-21 19:10:34 +04:00
LisoUseInAIKyrios
c2a099d1f4 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/music/src/main/java/app/revanced/extension/music/settings/GoogleApiActivityHook.java
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/checks/Check.java
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/BaseActivityHook.java
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ColorPickerPreference.java
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/CustomDialogListPreference.java
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/preference/ToolbarPreferenceFragment.java
#	extensions/twitch/src/main/java/app/revanced/extension/twitch/settings/TwitchActivityHook.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/AlternativeThumbnailsPatch.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/HidePlayerOverlayButtonsPatch.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/theme/SeekbarColorPatch.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/SearchViewController.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/YouTubeActivityHook.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ExternalDownloaderPreference.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/objects/SegmentCategoryListPreference.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/NewSegmentLayout.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/SkipSponsorButton.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/sponsorblock/ui/SponsorBlockViewController.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/videoplayer/VideoQualityDialogButton.java
#	patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentshuffle/PermanentShufflePatch.kt
#	patches/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/music/layout/upgradebutton/HideUpgradeButtonPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/music/misc/settings/SettingsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt
2025-09-21 18:01:08 +04:00
LisoUseInAIKyrios
d906046a52 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ButtonsFilter.java
#	patches/src/main/kotlin/app/revanced/patches/music/layout/navigationbar/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/player/fullscreen/OpenVideosFullscreenPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/spoofappversion/SpoofAppVersionPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/fix/backtoexitgesture/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/fix/backtoexitgesture/FixBackToExitGesturePatch.kt
2025-09-20 19:32:22 +04:00
LisoUseInAIKyrios
765957f2c9 finish merge 2025-09-20 17:56:33 +04:00
LisoUseInAIKyrios
8e64416f14 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/api/patches.api
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt
2025-09-20 17:50:31 +04:00
LisoUseInAIKyrios
6a5b204f8e fix SB create/voting buttons (merge error?) 2025-09-19 11:39:59 +04:00
LisoUseInAIKyrios
b99789b1cd unofficial 20.38 2025-09-19 11:08:48 +04:00
LisoUseInAIKyrios
bb671766f6 finish merge 2025-09-18 10:18:33 +04:00
LisoUseInAIKyrios
97ce498368 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LithoFilterPatch.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter.java
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/viber/ads/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt
2025-09-18 10:18:25 +04:00
LisoUseInAIKyrios
dcaa6feda0 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-16 17:27:38 +04:00
LisoUseInAIKyrios
e8d56c85cc Finish merge 2025-09-16 12:00:02 +04:00
LisoUseInAIKyrios
04401899f4 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	extensions/shared/library/src/main/java/app/revanced/extension/shared/spoof/requests/StreamingDataRequest.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/LicenseActivityHook.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java
#	extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/preference/ReVancedPreferenceFragment.java
#	patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentrepeat/PermanentRepeatPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/interaction/downloads/DownloadsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/seekbar/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/layout/theme/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/extension/hooks/ApplicationInitHook.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/misc/settings/SettingsPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt
2025-09-16 11:59:40 +04:00
LisoUseInAIKyrios
e52a9509e2 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-15 11:07:48 +04:00
LisoUseInAIKyrios
c6d84744ca Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-14 18:06:59 +04:00
LisoUseInAIKyrios
d3fae2a3e7 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-09-14 15:58:19 +04:00
LisoUseInAIKyrios
6ddf0583a4 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/api/patches.api
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt
2025-09-14 15:52:54 +04:00
LisoUseInAIKyrios
77864f41f4 unofficial 20.37 support 2025-09-14 14:22:01 +04:00
LisoUseInAIKyrios
a352a05db6 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters
# Conflicts:
#	patches/api/patches.api
#	patches/src/main/kotlin/app/revanced/patches/music/interaction/permanentshuffle/PermanentShufflePatch.kt
#	patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/music/misc/spoof/SpoofClientPatch.kt
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/Fingerprints.kt
#	patches/src/main/kotlin/app/revanced/patches/shared/misc/spoof/SpoofVideoStreamsPatch.kt
2025-09-14 02:35:21 +04:00
LisoUseInAIKyrios
724e6d61b2 feat: Update to patcher v22 2025-09-12 20:43:20 +04:00
1027 changed files with 79703 additions and 71120 deletions

View File

@@ -25,11 +25,12 @@ jobs:
- name: Build - name: Build
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid --no-daemon run: ./gradlew :patches:buildAndroid --no-daemon
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v5
with: with:
name: revanced-patches name: revanced-patches
path: patches/build/libs path: patches/build/libs

View File

@@ -2,7 +2,7 @@ name: Pull strings
on: on:
schedule: schedule:
- cron: "0 */12 * * *" - cron: "0 0 * * 0"
workflow_dispatch: workflow_dispatch:
jobs: jobs:

View File

@@ -16,10 +16,11 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Preprocess strings - name: Process strings
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
run: ./gradlew clean preprocessCrowdinStrings ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew processStringsForCrowdin
- name: Push strings - name: Push strings
uses: crowdin/github-action@v2 uses: crowdin/github-action@v2

View File

@@ -31,11 +31,12 @@ jobs:
- name: Build - name: Build
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid clean run: ./gradlew :patches:buildAndroid clean
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v5 uses: actions/setup-node@v6
with: with:
node-version: 'lts/*' node-version: 'lts/*'
cache: 'npm' cache: 'npm'
@@ -55,6 +56,8 @@ jobs:
id: release id: release
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
- name: Attest - name: Attest
if: steps.release.outputs.new_release_published == 'true' if: steps.release.outputs.new_release_published == 'true'

View File

@@ -1,3 +1,560 @@
# [5.50.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.3...v5.50.0-dev.4) (2026-01-27)
### Bug Fixes
* **Instagram - Open links externally:** Fix patch by handling >4-bit register ([#6538](https://github.com/ReVanced/revanced-patches/issues/6538)) ([f681a6f](https://github.com/ReVanced/revanced-patches/commit/f681a6ffd45f05a61743e7d272cd68c4b743be42))
# [5.50.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.2...v5.50.0-dev.3) (2026-01-26)
### Features
* **Kleinanzeigen:** Add `Hide ads` patch ([#6533](https://github.com/ReVanced/revanced-patches/issues/6533)) ([bd6e544](https://github.com/ReVanced/revanced-patches/commit/bd6e544007d539ac2eb890d9bdcb6850435f96cb))
# [5.50.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.50.0-dev.1...v5.50.0-dev.2) (2026-01-25)
### Bug Fixes
* **Letterboxd - Hide ads:** Fix patch by returning the correct return type ([#6527](https://github.com/ReVanced/revanced-patches/issues/6527)) ([80c34b9](https://github.com/ReVanced/revanced-patches/commit/80c34b9d74a42018a0cd52b4a584ee71206bf963))
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-25)
### Bug Fixes
* **Strava:** Fix `Add media download` patch ([#6526](https://github.com/ReVanced/revanced-patches/issues/6526)) ([dc9e68b](https://github.com/ReVanced/revanced-patches/commit/dc9e68ba574dd9f35cd742cb63193c5d875addde))
### Features
* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](https://github.com/ReVanced/revanced-patches/commit/421cb2899ef5c0f100fb8007bae8b89137d0e41c))
* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](https://github.com/ReVanced/revanced-patches/commit/66b0852f8fa57c82b09997337a304374883d8ba5))
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](https://github.com/ReVanced/revanced-patches/commit/5edd9dccae3b1ab4edf19771a771812e3c9ccf80))
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.49.0-dev.1...v5.50.0-dev.1) (2026-01-22)
### Features
* **Nothing X:** Add `Show K1 token(s)` patch ([#6490](https://github.com/ReVanced/revanced-patches/issues/6490)) ([421cb28](https://github.com/ReVanced/revanced-patches/commit/421cb2899ef5c0f100fb8007bae8b89137d0e41c))
* **Strava:** Add `Hide distractions` patch ([#6479](https://github.com/ReVanced/revanced-patches/issues/6479)) ([66b0852](https://github.com/ReVanced/revanced-patches/commit/66b0852f8fa57c82b09997337a304374883d8ba5))
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([5edd9dc](https://github.com/ReVanced/revanced-patches/commit/5edd9dccae3b1ab4edf19771a771812e3c9ccf80))
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.50.0-dev.1) (2026-01-22)
### Features
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([89645dc](https://github.com/ReVanced/revanced-patches/commit/89645dcc2e13603b8f2fedb5e16231cb396e5965))
# [5.49.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.1-dev.1...v5.49.0-dev.1) (2026-01-22)
### Features
* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](https://github.com/ReVanced/revanced-patches/commit/71ce8230a959dcaf2d8cd5dad1a4f21b88819aa0))
## [5.48.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.48.1-dev.1) (2026-01-21)
### Bug Fixes
* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](https://github.com/ReVanced/revanced-patches/commit/5b5c50254d533faa0e04d542f4859cbef610713e))
# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19)
### Bug Fixes
* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](https://github.com/ReVanced/revanced-patches/commit/eecc44b9567bf2ca72ac99e0dafa483a6803c0f9))
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154))
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8))
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](https://github.com/ReVanced/revanced-patches/commit/71c6cb569ebf7b93cf73ee391839e5220557ce7c))
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](https://github.com/ReVanced/revanced-patches/commit/a429824bb77b49aea14b0b54f2204ae24d5209a1))
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](https://github.com/ReVanced/revanced-patches/commit/87247590de3db74680cb02ba1d87bf683b2269e2))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](https://github.com/ReVanced/revanced-patches/commit/dc69f2433e2650654e2dffdd76b0b0c8a52bf515))
### Features
* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305))
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](https://github.com/ReVanced/revanced-patches/commit/83c0127ebb8f53ab8a067758619faaac5596c145))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c))
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](https://github.com/ReVanced/revanced-patches/commit/6bb62811493da04812cc3e392e68d874f95cbef9))
* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](https://github.com/ReVanced/revanced-patches/commit/8725a49ba3a06fee0280ffcf4be62cd960cd301e))
* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](https://github.com/ReVanced/revanced-patches/commit/18c0b04f0cd1bf8cd78b05af3b8ebe3a6a5f9e48))
* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](https://github.com/ReVanced/revanced-patches/commit/3401467a6d49fc75b6757a15e5c848330c1b7307))
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc))
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](https://github.com/ReVanced/revanced-patches/commit/e0f33468e6e96b9f10cf35ec67622d6488528c90))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](https://github.com/ReVanced/revanced-patches/commit/315931cbf8f61cd4b3a54ace1ff03685d748614c))
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](https://github.com/ReVanced/revanced-patches/commit/4c4ba1c78c9f4568a2b572f5c69e9c6c734e1a7f))
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](https://github.com/ReVanced/revanced-patches/commit/778d13ce8b28ca6df3a665530320e4a21a27ae44))
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](https://github.com/ReVanced/revanced-patches/commit/c47beae21376dd17ab8bc09afe73e9094481bde9))
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc))
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](https://github.com/ReVanced/revanced-patches/commit/8f3f4c95bb8f151fc9a2c272bf7d0e905c2f01fc))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567))
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](https://github.com/ReVanced/revanced-patches/commit/19f146c01dc381b3cccd61e61ba4901872ff12d8))
# [5.48.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.12...v5.48.0-dev.13) (2026-01-19)
### Features
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](https://github.com/ReVanced/revanced-patches/commit/83c0127ebb8f53ab8a067758619faaac5596c145))
# [5.48.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.11...v5.48.0-dev.12) (2026-01-19)
### Features
* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](https://github.com/ReVanced/revanced-patches/commit/18c0b04f0cd1bf8cd78b05af3b8ebe3a6a5f9e48))
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](https://github.com/ReVanced/revanced-patches/commit/4c4ba1c78c9f4568a2b572f5c69e9c6c734e1a7f))
# [5.48.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.10...v5.48.0-dev.11) (2026-01-19)
### Features
* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](https://github.com/ReVanced/revanced-patches/commit/8725a49ba3a06fee0280ffcf4be62cd960cd301e))
# [5.48.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.9...v5.48.0-dev.10) (2026-01-19)
### Bug Fixes
* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](https://github.com/ReVanced/revanced-patches/commit/eecc44b9567bf2ca72ac99e0dafa483a6803c0f9))
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](https://github.com/ReVanced/revanced-patches/commit/87247590de3db74680cb02ba1d87bf683b2269e2))
### Features
* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](https://github.com/ReVanced/revanced-patches/commit/3401467a6d49fc75b6757a15e5c848330c1b7307))
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](https://github.com/ReVanced/revanced-patches/commit/778d13ce8b28ca6df3a665530320e4a21a27ae44))
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](https://github.com/ReVanced/revanced-patches/commit/19f146c01dc381b3cccd61e61ba4901872ff12d8))
# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08)
### Features
* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c))
# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04)
### Features
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc))
# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04)
### Features
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567))
# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04)
### Bug Fixes
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8))
# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30)
### Bug Fixes
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154))
# [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29)
### Features
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](https://github.com/ReVanced/revanced-patches/commit/c47beae21376dd17ab8bc09afe73e9094481bde9))
# [5.48.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.2...v5.48.0-dev.3) (2025-12-28)
### Bug Fixes
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](https://github.com/ReVanced/revanced-patches/commit/71c6cb569ebf7b93cf73ee391839e5220557ce7c))
### Features
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](https://github.com/ReVanced/revanced-patches/commit/6bb62811493da04812cc3e392e68d874f95cbef9))
# [5.48.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.1...v5.48.0-dev.2) (2025-12-27)
### Features
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](https://github.com/ReVanced/revanced-patches/commit/8f3f4c95bb8f151fc9a2c272bf7d0e905c2f01fc))
# [5.48.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0-dev.1) (2025-12-23)
### Bug Fixes
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](https://github.com/ReVanced/revanced-patches/commit/a429824bb77b49aea14b0b54f2204ae24d5209a1))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](https://github.com/ReVanced/revanced-patches/commit/dc69f2433e2650654e2dffdd76b0b0c8a52bf515))
### Features
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](https://github.com/ReVanced/revanced-patches/commit/e0f33468e6e96b9f10cf35ec67622d6488528c90))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](https://github.com/ReVanced/revanced-patches/commit/315931cbf8f61cd4b3a54ace1ff03685d748614c))
# [5.47.0](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0) (2025-12-18)
### Bug Fixes
* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](https://github.com/ReVanced/revanced-patches/commit/bb745b555b3808b7679c5995319aa365630fbd76))
* **Lightroom:** Add `Disable version check` patch to fix opening the app ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](https://github.com/ReVanced/revanced-patches/commit/018d176914a06a30e9007a3eb2e6b0f459078413))
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](https://github.com/ReVanced/revanced-patches/commit/f8bd1239cc0f0bd1c2dca39f846951bf512891e3))
* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](https://github.com/ReVanced/revanced-patches/commit/34830ba63b436146064f0f89f948d51cd0cb9146))
* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](https://github.com/ReVanced/revanced-patches/commit/ded83702077701aac8a8749d71bf7376427f37d6))
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](https://github.com/ReVanced/revanced-patches/commit/9495cf49ef8a872be64de6c971c1919b4b9a8720))
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](https://github.com/ReVanced/revanced-patches/commit/654d091e650cda37650b57cbf3ba6f1cdd6d47d3))
### Features
* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](https://github.com/ReVanced/revanced-patches/commit/1f4f252c81e9a89267f6e37548e66027b1bc1a1a))
* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](https://github.com/ReVanced/revanced-patches/commit/6bd7dca75bd2ea335a596aa93a8b767d39be5f83))
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](https://github.com/ReVanced/revanced-patches/commit/0ea3491227fc50c03555d43d3fec78eb82906b26))
* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](https://github.com/ReVanced/revanced-patches/commit/94ae84ad0fc3a9197c82d5356301d464730c3b17))
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](https://github.com/ReVanced/revanced-patches/commit/2f0de15e67e4f99ed6ecdc136d04cceb23b0d069))
* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](https://github.com/ReVanced/revanced-patches/commit/0928dcd00dc2a9c1eef9a23c1e26ff5dc9ee670a))
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](https://github.com/ReVanced/revanced-patches/commit/0af0ee92c48bb2ffc332197e05439e20c5c05d83))
* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](https://github.com/ReVanced/revanced-patches/commit/847ee189a971e6d4a99823998569f8e561b8319c))
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](https://github.com/ReVanced/revanced-patches/commit/bbd8932b2e740aff96ba047332e541bff3e09436))
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](https://github.com/ReVanced/revanced-patches/commit/ac583d40d0f4c0e6544e3661ff3e82a25912f2b0))
* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](https://github.com/ReVanced/revanced-patches/commit/a5d197b9775b98d7a37bfdee9e5f726d5e04d8cf))
* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](https://github.com/ReVanced/revanced-patches/commit/02831a6069fc30ffa3a87f8e4de653d003a2187e))
* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](https://github.com/ReVanced/revanced-patches/commit/a7c220a4aea93ea7ae7005b5760443d7571c4228))
# [5.47.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.17...v5.47.0-dev.18) (2025-12-18)
### Features
* **Disney+ - SkipAds:** Add other package names the patch is compatible with ([#6372](https://github.com/ReVanced/revanced-patches/issues/6372)) ([1f4f252](https://github.com/ReVanced/revanced-patches/commit/1f4f252c81e9a89267f6e37548e66027b1bc1a1a))
# [5.47.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.16...v5.47.0-dev.17) (2025-12-18)
### Bug Fixes
* **Reddit - Hide ads:** Update patch for new versions of Reddit ([#6342](https://github.com/ReVanced/revanced-patches/issues/6342)) ([f8bd123](https://github.com/ReVanced/revanced-patches/commit/f8bd1239cc0f0bd1c2dca39f846951bf512891e3))
# [5.47.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.15...v5.47.0-dev.16) (2025-12-15)
### Bug Fixes
* **Lightroom:** Add `Disable version check` patch to fix opening the app ([#6315](https://github.com/ReVanced/revanced-patches/issues/6315)) ([018d176](https://github.com/ReVanced/revanced-patches/commit/018d176914a06a30e9007a3eb2e6b0f459078413))
### Features
* **IdAustria - Remove device integrity check:** Update patch to work with latest version ([#6360](https://github.com/ReVanced/revanced-patches/issues/6360)) ([0ea3491](https://github.com/ReVanced/revanced-patches/commit/0ea3491227fc50c03555d43d3fec78eb82906b26))
# [5.47.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.14...v5.47.0-dev.15) (2025-12-13)
### Bug Fixes
* **YouTube - Hide layout components:** Fix "Hide Subscribe button" in channel page not working ([#6363](https://github.com/ReVanced/revanced-patches/issues/6363)) ([ded8370](https://github.com/ReVanced/revanced-patches/commit/ded83702077701aac8a8749d71bf7376427f37d6))
# [5.47.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.13...v5.47.0-dev.14) (2025-12-13)
### Bug Fixes
* **Spotify:** Make patches work with latest versions again ([#6359](https://github.com/ReVanced/revanced-patches/issues/6359)) ([34830ba](https://github.com/ReVanced/revanced-patches/commit/34830ba63b436146064f0f89f948d51cd0cb9146))
# [5.47.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.12...v5.47.0-dev.13) (2025-12-10)
### Features
* **Peacock TV:** Add `Hide ads` patch ([#6348](https://github.com/ReVanced/revanced-patches/issues/6348)) ([847ee18](https://github.com/ReVanced/revanced-patches/commit/847ee189a971e6d4a99823998569f8e561b8319c))
# [5.47.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.11...v5.47.0-dev.12) (2025-12-08)
### Features
* **YouTube - Hide layout components:** Add "Hide Join button" and "Hide Subscribe button" options for channel page ([#6345](https://github.com/ReVanced/revanced-patches/issues/6345)) ([02831a6](https://github.com/ReVanced/revanced-patches/commit/02831a6069fc30ffa3a87f8e4de653d003a2187e))
# [5.47.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.10...v5.47.0-dev.11) (2025-12-08)
### Features
* **Disney+:** Add `Skip ads` patch ([#6343](https://github.com/ReVanced/revanced-patches/issues/6343)) ([6bd7dca](https://github.com/ReVanced/revanced-patches/commit/6bd7dca75bd2ea335a596aa93a8b767d39be5f83))
# [5.47.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.9...v5.47.0-dev.10) (2025-12-08)
### Features
* **YouTube - Hide Shorts components:** Add "Hide auto-dubbed label" and "Hide live preview" options ([#6334](https://github.com/ReVanced/revanced-patches/issues/6334)) ([a7c220a](https://github.com/ReVanced/revanced-patches/commit/a7c220a4aea93ea7ae7005b5760443d7571c4228))
# [5.47.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.8...v5.47.0-dev.9) (2025-12-08)
### Features
* **YouTube - Hide layout components:** Add "Hide cell divider", "Hide featured links", and "Hide featured videos" options ([#6335](https://github.com/ReVanced/revanced-patches/issues/6335)) ([a5d197b](https://github.com/ReVanced/revanced-patches/commit/a5d197b9775b98d7a37bfdee9e5f726d5e04d8cf))
# [5.47.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.7...v5.47.0-dev.8) (2025-12-08)
### Features
* **Instagram:** Add `Disable Reels scrolling` patch ([#6317](https://github.com/ReVanced/revanced-patches/issues/6317)) ([0928dcd](https://github.com/ReVanced/revanced-patches/commit/0928dcd00dc2a9c1eef9a23c1e26ff5dc9ee670a))
* **ProtonVPN:** Add `Remove delay` patch ([#6326](https://github.com/ReVanced/revanced-patches/issues/6326)) ([bbd8932](https://github.com/ReVanced/revanced-patches/commit/bbd8932b2e740aff96ba047332e541bff3e09436))
# [5.47.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.6...v5.47.0-dev.7) (2025-12-03)
### Features
* **Spoof SIM provider:** Spoof additional TelephonyManager methods ([#6293](https://github.com/ReVanced/revanced-patches/issues/6293)) ([ac583d4](https://github.com/ReVanced/revanced-patches/commit/ac583d40d0f4c0e6544e3661ff3e82a25912f2b0))
# [5.47.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.5...v5.47.0-dev.6) (2025-11-24)
### Features
* **Letterboxd:** Add `Hide ads` patch ([#6309](https://github.com/ReVanced/revanced-patches/issues/6309)) ([0af0ee9](https://github.com/ReVanced/revanced-patches/commit/0af0ee92c48bb2ffc332197e05439e20c5c05d83))
# [5.47.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.4...v5.47.0-dev.5) (2025-11-13)
### Bug Fixes
* **YouTube - Hide player flyout menu items:** Allow hiding audio menu with 'Android No SDK' client type ([9495cf4](https://github.com/ReVanced/revanced-patches/commit/9495cf49ef8a872be64de6c971c1919b4b9a8720))
# [5.47.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.3...v5.47.0-dev.4) (2025-11-12)
### Bug Fixes
* **YouTube - Sanitize sharing links:** Handle non hierarchical urls ([654d091](https://github.com/ReVanced/revanced-patches/commit/654d091e650cda37650b57cbf3ba6f1cdd6d47d3))
# [5.47.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.2...v5.47.0-dev.3) (2025-11-12)
### Features
* **Instagram:** Add `Disable auto story flipping` patch ([#6262](https://github.com/ReVanced/revanced-patches/issues/6262)) ([2f0de15](https://github.com/ReVanced/revanced-patches/commit/2f0de15e67e4f99ed6ecdc136d04cceb23b0d069))
# [5.47.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.47.0-dev.1...v5.47.0-dev.2) (2025-11-12)
### Bug Fixes
* **Instagram - Disable signature check:** Change patch to default excluded ([#6283](https://github.com/ReVanced/revanced-patches/issues/6283)) ([bb745b5](https://github.com/ReVanced/revanced-patches/commit/bb745b555b3808b7679c5995319aa365630fbd76))
# [5.47.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.46.0...v5.47.0-dev.1) (2025-11-12)
### Features
* **Instagram:** Add `Anonymous story viewing` patch ([#6263](https://github.com/ReVanced/revanced-patches/issues/6263)) ([94ae84a](https://github.com/ReVanced/revanced-patches/commit/94ae84ad0fc3a9197c82d5356301d464730c3b17))
# [5.46.0](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0) (2025-11-10)
### Bug Fixes
* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](https://github.com/ReVanced/revanced-patches/commit/f238ae9895000f01d1dccb800cc8efde0d5362bd))
* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](https://github.com/ReVanced/revanced-patches/commit/e030e9c07a7748e117ac44f6776a9f6317b20623))
* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](https://github.com/ReVanced/revanced-patches/commit/59d85b28a7fcb285ff5f2bb6ae654020d76b2019))
* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](https://github.com/ReVanced/revanced-patches/commit/57263538c79f5a561c449229ac8e068c641285d3))
* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](https://github.com/ReVanced/revanced-patches/commit/582144026d28e57bb7adcbba39244f3c7cdbc0f3))
* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](https://github.com/ReVanced/revanced-patches/commit/d390b54dab92d75b4e0d3e38344eae489dd69d98))
* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](https://github.com/ReVanced/revanced-patches/commit/76dcfaefd8679e45a70f265b0239436e60c055cf))
### Features
* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](https://github.com/ReVanced/revanced-patches/commit/2e9d6959c94df7588b9e34b18770e9f437e91926))
* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](https://github.com/ReVanced/revanced-patches/commit/a52c0153b12c3f6f0ad260e03d2e9850c0466392))
* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](https://github.com/ReVanced/revanced-patches/commit/da4cf940911a4406e2c9dd558b60305385a80c61))
* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](https://github.com/ReVanced/revanced-patches/commit/858edbf3e7f394fcc766d767c8dc54cf5ba24370))
* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](https://github.com/ReVanced/revanced-patches/commit/ab808aeb773592cb26c848d8456478a346ec3bad))
* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](https://github.com/ReVanced/revanced-patches/commit/7a18ebc7ab74ba30c5d5284a4856c55cdfc31097))
# [5.46.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.9...v5.46.0-dev.10) (2025-11-09)
### Features
* **YouTube - Hide layout components:** Add video description "Hide Featured content" and "Hide Subscribe button" ([#6253](https://github.com/ReVanced/revanced-patches/issues/6253)) ([da4cf94](https://github.com/ReVanced/revanced-patches/commit/da4cf940911a4406e2c9dd558b60305385a80c61))
# [5.46.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.8...v5.46.0-dev.9) (2025-11-09)
### Features
* **YouTube Music:** Add `Change miniplayer color` patch ([#6259](https://github.com/ReVanced/revanced-patches/issues/6259)) ([ab808ae](https://github.com/ReVanced/revanced-patches/commit/ab808aeb773592cb26c848d8456478a346ec3bad))
# [5.46.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.7...v5.46.0-dev.8) (2025-11-09)
### Features
* **YouTube Music:** Add `Hide buttons` patch ([#6255](https://github.com/ReVanced/revanced-patches/issues/6255)) ([7a18ebc](https://github.com/ReVanced/revanced-patches/commit/7a18ebc7ab74ba30c5d5284a4856c55cdfc31097))
# [5.46.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.6...v5.46.0-dev.7) (2025-11-08)
### Bug Fixes
* **YouTube - Settings:** Add additional languages to ReVanced language preference ([d390b54](https://github.com/ReVanced/revanced-patches/commit/d390b54dab92d75b4e0d3e38344eae489dd69d98))
# [5.46.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.5...v5.46.0-dev.6) (2025-11-08)
### Features
* **YouTube - Debugging:** Add setting to block experimental client flags ([#6196](https://github.com/ReVanced/revanced-patches/issues/6196)) ([2e9d695](https://github.com/ReVanced/revanced-patches/commit/2e9d6959c94df7588b9e34b18770e9f437e91926))
# [5.46.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.4...v5.46.0-dev.5) (2025-11-07)
### Bug Fixes
* **Duolingo - Disable ads:** Constrain patch to last working app target ([f238ae9](https://github.com/ReVanced/revanced-patches/commit/f238ae9895000f01d1dccb800cc8efde0d5362bd))
* **Instagram - Hide navigation buttons:** Constrain patch to last working app target ([e030e9c](https://github.com/ReVanced/revanced-patches/commit/e030e9c07a7748e117ac44f6776a9f6317b20623))
* **Spotify - Hide Create button:** Remove obsolete patch that is no longer needed ([#6252](https://github.com/ReVanced/revanced-patches/issues/6252)) ([59d85b2](https://github.com/ReVanced/revanced-patches/commit/59d85b28a7fcb285ff5f2bb6ae654020d76b2019))
# [5.46.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.3...v5.46.0-dev.4) (2025-11-07)
### Bug Fixes
* **YouTube - Check watch history domain name resolution:** Fix false positive warning message if the internet connection fails halfway into the DNS check ([5726353](https://github.com/ReVanced/revanced-patches/commit/57263538c79f5a561c449229ac8e068c641285d3))
# [5.46.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.2...v5.46.0-dev.3) (2025-11-06)
### Bug Fixes
* **YouTube - Hide layout components:** Fix "Hide Hype points" ([#6247](https://github.com/ReVanced/revanced-patches/issues/6247)) ([5821440](https://github.com/ReVanced/revanced-patches/commit/582144026d28e57bb7adcbba39244f3c7cdbc0f3))
# [5.46.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.46.0-dev.1...v5.46.0-dev.2) (2025-11-04)
### Bug Fixes
* **YouTube - Settings:** Resolve settings search crash when searching for specific words ([#6231](https://github.com/ReVanced/revanced-patches/issues/6231)) ([76dcfae](https://github.com/ReVanced/revanced-patches/commit/76dcfaefd8679e45a70f265b0239436e60c055cf))
# [5.46.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.45.0...v5.46.0-dev.1) (2025-11-04)
### Features
* **YouTube - Hide layout components:** Add "Hide Hype points" ([#6230](https://github.com/ReVanced/revanced-patches/issues/6230)) ([a52c015](https://github.com/ReVanced/revanced-patches/commit/a52c0153b12c3f6f0ad260e03d2e9850c0466392))
* **YouTube - Hide player flyout menu items:** Add "Hide Listen with YouTube Music" ([#6232](https://github.com/ReVanced/revanced-patches/issues/6232)) ([858edbf](https://github.com/ReVanced/revanced-patches/commit/858edbf3e7f394fcc766d767c8dc54cf5ba24370))
# [5.45.0](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0) (2025-11-01)
### Bug Fixes
* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](https://github.com/ReVanced/revanced-patches/commit/c73a03c9e18a12262939c974cdf16221221d1487))
* **TikTok - Downloads:** Fix download path setting ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](https://github.com/ReVanced/revanced-patches/commit/3e4990afff4c86b93970b153db713ad0f813124d))
* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](https://github.com/ReVanced/revanced-patches/commit/a0c56049510ce040e1ccd49257864672c343344d))
* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](https://github.com/ReVanced/revanced-patches/commit/6d01863ec70617d9abc864ce6686ed9764dd151d))
* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](https://github.com/ReVanced/revanced-patches/commit/292fae440c6d5694c5e84407becec2d91f1fd156))
* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](https://github.com/ReVanced/revanced-patches/commit/13cf1724bf2f946c7129cab0db96721c90f9fe89))
### Features
* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](https://github.com/ReVanced/revanced-patches/commit/5f23bfe833c6e01617a7dbc5325b4a3fb931e53e))
* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](https://github.com/ReVanced/revanced-patches/commit/ef44eaa119b9d6c5faec051e22d20f883d0da4f1))
* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](https://github.com/ReVanced/revanced-patches/commit/e9f45ce92695d5857473ff71c14b190bded28a73))
# [5.45.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.5...v5.45.0-dev.6) (2025-11-01)
### Features
* **Spoof video streams:** Add experimental "Android No SDK" client type ([5f23bfe](https://github.com/ReVanced/revanced-patches/commit/5f23bfe833c6e01617a7dbc5325b4a3fb931e53e))
# [5.45.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.4...v5.45.0-dev.5) (2025-11-01)
### Bug Fixes
* **TikTok - Downloads:** Fix download path setting ([#6191](https://github.com/ReVanced/revanced-patches/issues/6191)) ([3e4990a](https://github.com/ReVanced/revanced-patches/commit/3e4990afff4c86b93970b153db713ad0f813124d))
* **YouTube - Spoof video streams:** Remove spoof stream audio selector that no longer works ([292fae4](https://github.com/ReVanced/revanced-patches/commit/292fae440c6d5694c5e84407becec2d91f1fd156))
# [5.45.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.3...v5.45.0-dev.4) (2025-10-30)
### Bug Fixes
* **YouTube - Change header:** Do not mirror header graphic with RTL languages ([a0c5604](https://github.com/ReVanced/revanced-patches/commit/a0c56049510ce040e1ccd49257864672c343344d))
# [5.45.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.2...v5.45.0-dev.3) (2025-10-27)
### Features
* **YouTube - Change Header:** Use SVG for header logo ([#6178](https://github.com/ReVanced/revanced-patches/issues/6178)) ([e9f45ce](https://github.com/ReVanced/revanced-patches/commit/e9f45ce92695d5857473ff71c14b190bded28a73))
# [5.45.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.45.0-dev.1...v5.45.0-dev.2) (2025-10-26)
### Bug Fixes
* **YouTube - Force original audio:** Fall back to visionOS and not Android Studio if Android VR is not available ([6d01863](https://github.com/ReVanced/revanced-patches/commit/6d01863ec70617d9abc864ce6686ed9764dd151d))
* **YouTube Music - Hide category bar:** Correctly hide the category bar in newer app targets ([#6175](https://github.com/ReVanced/revanced-patches/issues/6175)) ([13cf172](https://github.com/ReVanced/revanced-patches/commit/13cf1724bf2f946c7129cab0db96721c90f9fe89))
# [5.45.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.44.0...v5.45.0-dev.1) (2025-10-26)
### Bug Fixes
* **Instagram:** Update failing fingerprints on newer versions ([#6181](https://github.com/ReVanced/revanced-patches/issues/6181)) ([c73a03c](https://github.com/ReVanced/revanced-patches/commit/c73a03c9e18a12262939c974cdf16221221d1487))
### Features
* **TikTok:** Add `Sanitize sharing links` patch ([#6176](https://github.com/ReVanced/revanced-patches/issues/6176)) ([ef44eaa](https://github.com/ReVanced/revanced-patches/commit/ef44eaa119b9d6c5faec051e22d20f883d0da4f1))
# [5.44.0](https://github.com/ReVanced/revanced-patches/compare/v5.43.1...v5.44.0) (2025-10-24)
### Bug Fixes
* **Google Photos - Spoof features:** Add support for Pixel 10 devices ([#6161](https://github.com/ReVanced/revanced-patches/issues/6161)) ([754b719](https://github.com/ReVanced/revanced-patches/commit/754b71959a0155413eb33cf1bdc2c8976eaca634))
* **X / Twitter - Change link sharing domain:** Use bytecode patching to resolve patching with Manager ([#6125](https://github.com/ReVanced/revanced-patches/issues/6125)) ([0af8c8a](https://github.com/ReVanced/revanced-patches/commit/0af8c8a766ae4ba6926404d59da2f14d649f91f7))
* **YouTube - Hide layout components:** Hide new kind of community post ([#6146](https://github.com/ReVanced/revanced-patches/issues/6146)) ([cfd244b](https://github.com/ReVanced/revanced-patches/commit/cfd244b4088daacd2788ec38357ac521e4b296d5))
* **YouTube Music:** Resolve patching 7.29 target ([2e4c6fd](https://github.com/ReVanced/revanced-patches/commit/2e4c6fdcadeef45a80733e374421d52e5e8af910))
### Features
* Add `Custom network security` patch ([#6151](https://github.com/ReVanced/revanced-patches/issues/6151)) ([e7336d2](https://github.com/ReVanced/revanced-patches/commit/e7336d2ef361cc5d6fe6e8442b36d9cf1f542931))
* **Duolingo - Enable debug menu:** Support latest app target ([#6163](https://github.com/ReVanced/revanced-patches/issues/6163)) ([08baa19](https://github.com/ReVanced/revanced-patches/commit/08baa19b4a62e62bd103d177c3f4454de199cf16))
* **Duolingo:** Add `Skip energy recharge ads` patch ([#6167](https://github.com/ReVanced/revanced-patches/issues/6167)) ([591e106](https://github.com/ReVanced/revanced-patches/commit/591e106098c6eff431b8b7ac7d985ce7373d701e))
* **Samsung Radio:** Add `Disable device checks` patch ([#6145](https://github.com/ReVanced/revanced-patches/issues/6145)) ([de97562](https://github.com/ReVanced/revanced-patches/commit/de97562c5ddc8ec707761c1e04e74c4e18f9c158))
# [5.44.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.3...v5.44.0-dev.4) (2025-10-24) # [5.44.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.44.0-dev.3...v5.44.0-dev.4) (2025-10-24)

View File

@@ -97,9 +97,9 @@ Thank you for considering contributing to ReVanced Patches. You can find the con
To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation). To build ReVanced Patches, you can follow the [ReVanced documentation](https://github.com/ReVanced/revanced-documentation).
## 📜 Licence ## 📜 License
ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information. ReVanced Patches is licensed under the GPLv3 license. Please see the [license file](LICENSE) for more information.
[tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files. [tl;dr](https://www.tldrlegal.com/license/gnu-general-public-license-v3-gpl-3) you may copy, distribute and modify ReVanced Patches as long as you track changes/dates in source files.
Any modifications to ReVanced Patches must also be made available under the GPL, Any modifications to ReVanced Patches must also be made available under the GPL,
along with build & install instructions. along with build & install instructions.

View File

@@ -1,8 +1,9 @@
project_id_env: "CROWDIN_PROJECT_ID" project_id_env: "CROWDIN_PROJECT_ID"
api_token_env: "CROWDIN_PERSONAL_TOKEN" api_token_env: "CROWDIN_PERSONAL_TOKEN"
preserve_hierarchy: false preserve_hierarchy: true
files: files:
- source: patches/src/main/resources/addresources/values/strings.xml - source: patches/src/main/resources/addresources/values/strings.xml
dest: patches.xml
translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml
skip_untranslated_strings: true skip_untranslated_strings: true

View File

@@ -0,0 +1,20 @@
android {
namespace = "app.revanced.extension"
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
aidl = true
}
}
dependencies {
compileOnly(libs.annotation)
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,8 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
interface IExpressIntegrityService {
oneway void requestIntegrityToken(in Bundle request, IExpressIntegrityServiceCallback callback) = 2;
}

View File

@@ -0,0 +1,5 @@
package com.google.android.play.core.integrity.protocol;
interface IExpressIntegrityServiceCallback {
oneway void onRequestExpressIntegrityTokenResult(in Bundle result) = 2;
}

View File

@@ -0,0 +1,8 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
interface IIntegrityService {
oneway void requestIntegrityToken(in Bundle request, IIntegrityServiceCallback callback) = 1;
}

View File

@@ -0,0 +1,7 @@
package com.google.android.play.core.integrity.protocol;
import android.os.Bundle;
interface IIntegrityServiceCallback {
oneway void onResult(in Bundle result) = 1;
}

View File

@@ -0,0 +1,10 @@
package android.ext;
/** @hide */
// Int values that are assigned to packages in this interface can be retrieved at runtime from
// ApplicationInfo.ext().getPackageId() or from AndroidPackage.ext().getPackageId() (in system_server).
//
// PackageIds are assigned to parsed APKs only after they are verified, either by a certificate check
// or by a check that the APK is stored on an immutable OS partition.
public interface PackageId {
String PLAY_STORE_NAME = "com.android.vending";
}

View File

@@ -0,0 +1,62 @@
package android.os;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.FileDescriptor;
/** @hide */
public class BinderWrapper implements IBinder {
protected final IBinder base;
public BinderWrapper(IBinder base) {
this.base = base;
}
@Override
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
return base.transact(code, data, reply, flags);
}
@Nullable
@Override
public IInterface queryLocalInterface(@NonNull String descriptor) {
return base.queryLocalInterface(descriptor);
}
@Nullable
@Override
public String getInterfaceDescriptor() throws RemoteException {
return base.getInterfaceDescriptor();
}
@Override
public boolean pingBinder() {
return base.pingBinder();
}
@Override
public boolean isBinderAlive() {
return base.isBinderAlive();
}
@Override
public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
base.dump(fd, args);
}
@Override
public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException {
base.dumpAsync(fd, args);
}
@Override
public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException {
base.linkToDeath(recipient, flags);
}
@Override
public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
return base.unlinkToDeath(recipient, flags);
}
}

View File

@@ -0,0 +1,41 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.os.FakeBackgroundHandler;
import com.google.android.play.core.integrity.protocol.IIntegrityService;
import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback;
class ClassicPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
ClassicPlayIntegrityServiceWrapper(IBinder base) {
super(base);
requestIntegrityTokenTxnCode = 2; // IIntegrityService.Stub.TRANSACTION_requestIntegrityToken
}
static class TokenRequestStub extends IIntegrityService.Stub {
public void requestIntegrityToken(Bundle request, IIntegrityServiceCallback callback) {
Runnable r = () -> {
var result = new Bundle();
// https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/IntegrityErrorCode.html#API_NOT_AVAILABLE
final int API_NOT_AVAILABLE = -1;
result.putInt("error", API_NOT_AVAILABLE);
try {
callback.onResult(result);
} catch (RemoteException e) {
Log.e("IIntegrityService.Stub", "", e);
}
};
FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
}
};
@Override
protected Binder createTokenRequestStub() {
return new TokenRequestStub();
}
}

View File

@@ -0,0 +1,48 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.os.Binder;
import android.os.BinderWrapper;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import androidx.annotation.Nullable;
abstract class PlayIntegrityServiceWrapper extends BinderWrapper {
final String TAG;
protected int requestIntegrityTokenTxnCode;
public PlayIntegrityServiceWrapper(IBinder base) {
super(base);
TAG = getClass().getSimpleName();
}
protected abstract Binder createTokenRequestStub();
@Override
public boolean transact(int code, Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {
if (code == requestIntegrityTokenTxnCode) {
if (maybeStubOutIntegrityTokenRequest(code, data, reply, flags)) {
return true;
}
}
return super.transact(code, data, reply, flags);
}
private boolean maybeStubOutIntegrityTokenRequest(int code, Parcel data, @Nullable Parcel reply, int flags) {
Log.d(TAG, "integrity token request detected");
try {
createTokenRequestStub().transact(code, data, reply, flags);
} catch (RemoteException e) {
// this is a local call
throw new IllegalStateException(e);
}
return true;
}
protected static long getTokenRequestResultDelay() {
return 500L;
}
}

View File

@@ -0,0 +1,35 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.content.Intent;
import android.content.ServiceConnection;
import android.ext.PackageId;
import android.os.IBinder;
import androidx.annotation.Nullable;
import app.grapheneos.gmscompat.lib.util.ServiceConnectionWrapper;
import java.util.function.UnaryOperator;
public class PlayIntegrityUtils {
public static @Nullable ServiceConnection maybeReplaceServiceConnection(Intent service, ServiceConnection orig) {
if (PackageId.PLAY_STORE_NAME.equals(service.getPackage())) {
UnaryOperator<IBinder> binderOverride = null;
final String CLASSIC_SERVICE =
"com.google.android.play.core.integrityservice.BIND_INTEGRITY_SERVICE";
final String STANDARD_SERVICE =
"com.google.android.play.core.expressintegrityservice.BIND_EXPRESS_INTEGRITY_SERVICE";
String action = service.getAction();
if (STANDARD_SERVICE.equals(action)) {
binderOverride = StandardPlayIntegrityServiceWrapper::new;
} else if (CLASSIC_SERVICE.equals(action)) {
binderOverride = ClassicPlayIntegrityServiceWrapper::new;
}
if (binderOverride != null) {
return new ServiceConnectionWrapper(orig, binderOverride);
}
}
return null;
}
}

View File

@@ -0,0 +1,42 @@
package app.grapheneos.gmscompat.lib.playintegrity;
import android.annotation.SuppressLint;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.internal.os.FakeBackgroundHandler;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityService;
import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback;
@SuppressLint("LongLogTag")
class StandardPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper {
StandardPlayIntegrityServiceWrapper(IBinder base) {
super(base);
requestIntegrityTokenTxnCode = 3; // IExpressIntegrityService.Stub.TRANSACTION_requestIntegrityToken
}
static class TokenRequestStub extends IExpressIntegrityService.Stub {
public void requestIntegrityToken(Bundle request, IExpressIntegrityServiceCallback callback) {
Runnable r = () -> {
var result = new Bundle();
// https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/StandardIntegrityErrorCode.html#API_NOT_AVAILABLE
final int API_NOT_AVAILABLE = -1;
result.putInt("error", API_NOT_AVAILABLE);
try {
callback.onRequestExpressIntegrityTokenResult(result);
} catch (RemoteException e) {
Log.e("IExpressIntegrityService.Stub", "", e);
}
};
FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay());
}
};
@Override
protected Binder createTokenRequestStub() {
return new TokenRequestStub();
}
}

View File

@@ -0,0 +1,49 @@
package app.grapheneos.gmscompat.lib.util;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.IBinder;
import java.util.function.UnaryOperator;
public class ServiceConnectionWrapper implements ServiceConnection {
private final ServiceConnection base;
private final UnaryOperator<IBinder> binderOverride;
public ServiceConnectionWrapper(ServiceConnection base, UnaryOperator<IBinder> binderOverride) {
this.base = base;
this.binderOverride = binderOverride;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
IBinder override = binderOverride.apply(service);
if (override != null) {
service = override;
}
}
base.onServiceConnected(name, service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
base.onServiceDisconnected(name);
}
@Override
public void onBindingDied(ComponentName name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
base.onBindingDied(name);
}
}
@Override
public void onNullBinding(ComponentName name) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
base.onNullBinding(name);
}
}
}

View File

@@ -0,0 +1,17 @@
package app.revanced.extension.playintegrity;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import app.grapheneos.gmscompat.lib.playintegrity.PlayIntegrityUtils;
public class DisablePlayIntegrityPatch {
public static boolean bindService(Context context, Intent service, ServiceConnection conn, int flags) {
ServiceConnection override = PlayIntegrityUtils.maybeReplaceServiceConnection(service, conn);
if (override != null) {
conn = override;
}
return context.bindService(service, conn, flags);
}
}

View File

@@ -0,0 +1,11 @@
package com.android.internal.os;
import android.os.Handler;
import android.os.Looper;
public class FakeBackgroundHandler {
public static Handler getHandler() {
return new Handler(Looper.getMainLooper());
}
}

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.music.patches;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class ChangeMiniplayerColorPatch {
/**
* Injection point
*/
public static boolean changeMiniplayerColor() {
return Settings.CHANGE_MINIPLAYER_COLOR.get();
}
}

View File

@@ -0,0 +1,49 @@
package app.revanced.extension.music.patches;
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
import android.view.View;
import android.view.ViewGroup;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideButtonsPatch {
/**
* Injection point
*/
public static int hideCastButton(int original) {
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
}
/**
* Injection point
*/
public static void hideCastButton(View view) {
hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON, view);
}
/**
* Injection point
*/
public static boolean hideHistoryButton(boolean original) {
return original && !Settings.HIDE_HISTORY_BUTTON.get();
}
/**
* Injection point
*/
public static void hideNotificationButton(View view) {
if (view.getParent() instanceof ViewGroup viewGroup) {
hideViewBy0dpUnderCondition(Settings.HIDE_NOTIFICATION_BUTTON, viewGroup);
}
}
/**
* Injection point
*/
public static void hideSearchButton(View view) {
hideViewBy0dpUnderCondition(Settings.HIDE_SEARCH_BUTTON, view);
}
}

View File

@@ -1,24 +0,0 @@
package app.revanced.extension.music.patches;
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
import android.view.View;
import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused")
public class HideCastButtonPatch {
/**
* Injection point
*/
public static int hideCastButton(int original) {
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
}
/**
* Injection point
*/
public static void hideCastButton(View view) {
hideViewBy0dpUnderCondition(Settings.HIDE_CAST_BUTTON.get(), view);
}
}

View File

@@ -1,5 +1,9 @@
package app.revanced.extension.music.patches; package app.revanced.extension.music.patches;
import static app.revanced.extension.shared.Utils.hideViewBy0dpUnderCondition;
import android.view.View;
import app.revanced.extension.music.settings.Settings; import app.revanced.extension.music.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -8,7 +12,7 @@ public class HideCategoryBarPatch {
/** /**
* Injection point * Injection point
*/ */
public static boolean hideCategoryBar() { public static void hideCategoryBar(View view) {
return Settings.HIDE_CATEGORY_BAR.get(); hideViewBy0dpUnderCondition(Settings.HIDE_CATEGORY_BAR, view);
} }
} }

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.music.patches.spoof; package app.revanced.extension.music.patches.spoof;
import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE; import static app.revanced.extension.music.settings.Settings.SPOOF_VIDEO_STREAMS_CLIENT_TYPE;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_NO_SDK;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32; import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48; import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_61_48;
import static app.revanced.extension.shared.spoof.ClientType.VISIONOS; import static app.revanced.extension.shared.spoof.ClientType.VISIONOS;
@@ -18,8 +19,9 @@ public class SpoofVideoStreamsPatch {
public static void setClientOrderToUse() { public static void setClientOrderToUse() {
List<ClientType> availableClients = List.of( List<ClientType> availableClients = List.of(
ANDROID_VR_1_43_32, ANDROID_VR_1_43_32,
ANDROID_VR_1_61_48, ANDROID_NO_SDK,
VISIONOS VISIONOS,
ANDROID_VR_1_61_48
); );
app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse( app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.setClientsToUse(

View File

@@ -11,6 +11,7 @@ import android.widget.Toolbar;
import app.revanced.extension.music.settings.preference.MusicPreferenceFragment; import app.revanced.extension.music.settings.preference.MusicPreferenceFragment;
import app.revanced.extension.music.settings.search.MusicSearchViewController; import app.revanced.extension.music.settings.search.MusicSearchViewController;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook; import app.revanced.extension.shared.settings.BaseActivityHook;
@@ -46,15 +47,7 @@ public class MusicActivityHook extends BaseActivityHook {
// Override the default YouTube Music theme to increase start padding of list items. // Override the default YouTube Music theme to increase start padding of list items.
// Custom style located in resources/music/values/style.xml // Custom style located in resources/music/values/style.xml
activity.setTheme(Utils.getResourceIdentifierOrThrow( activity.setTheme(Utils.getResourceIdentifierOrThrow(
"Theme.ReVanced.YouTubeMusic.Settings", "style")); ResourceType.STYLE, "Theme.ReVanced.YouTubeMusic.Settings"));
}
/**
* Returns the resource ID for the YouTube Music settings layout.
*/
@Override
protected int getContentViewResourceId() {
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
} }
/** /**

View File

@@ -4,20 +4,23 @@ import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent; import static app.revanced.extension.shared.settings.Setting.parent;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.EnumSetting; import app.revanced.extension.shared.settings.EnumSetting;
import app.revanced.extension.shared.spoof.ClientType; import app.revanced.extension.shared.spoof.ClientType;
public class Settings extends BaseSettings { public class Settings extends YouTubeAndMusicSettings {
// Ads // Ads
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true); public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);
public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true); public static final BooleanSetting HIDE_GET_PREMIUM_LABEL = new BooleanSetting("revanced_music_hide_get_premium_label", TRUE, true);
// General // General
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, false); public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_music_hide_cast_button", TRUE, true);
public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true); public static final BooleanSetting HIDE_CATEGORY_BAR = new BooleanSetting("revanced_music_hide_category_bar", FALSE, true);
public static final BooleanSetting HIDE_HISTORY_BUTTON = new BooleanSetting("revanced_music_hide_history_button", FALSE, true);
public static final BooleanSetting HIDE_SEARCH_BUTTON = new BooleanSetting("revanced_music_hide_search_button", FALSE, true);
public static final BooleanSetting HIDE_NOTIFICATION_BUTTON = new BooleanSetting("revanced_music_hide_notification_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_HOME_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_home_button", FALSE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR_HOME_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_home_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_SAMPLES_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_samples_button", FALSE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR_SAMPLES_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_samples_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BAR_EXPLORE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_explore_button", FALSE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR_EXPLORE_BUTTON = new BooleanSetting("revanced_music_hide_navigation_bar_explore_button", FALSE, true);
@@ -27,6 +30,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true); public static final BooleanSetting HIDE_NAVIGATION_BAR_LABEL = new BooleanSetting("revanced_music_hide_navigation_bar_labels", FALSE, true);
// Player // Player
public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("revanced_music_change_miniplayer_color", FALSE, true);
public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true); public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("revanced_music_play_permanent_repeat", FALSE, true);
// Miscellaneous // Miscellaneous

View File

@@ -0,0 +1,15 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:nothingx:stub"))
}
android {
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,590 @@
package app.revanced.extension.nothingx.patches;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Application;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Patches to expose the K1 token for Nothing X app to enable pairing with GadgetBridge.
*/
@SuppressWarnings("unused")
public class ShowK1TokensPatch {
private static final String TAG = "ReVanced";
private static final String PACKAGE_NAME = "com.nothing.smartcenter";
private static final String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e";
private static final String PREFS_NAME = "revanced_nothingx_prefs";
private static final String KEY_DONT_SHOW_DIALOG = "dont_show_k1_dialog";
// Colors
private static final int COLOR_BG = 0xFF1E1E1E;
private static final int COLOR_CARD = 0xFF2D2D2D;
private static final int COLOR_TEXT_PRIMARY = 0xFFFFFFFF;
private static final int COLOR_TEXT_SECONDARY = 0xFFB0B0B0;
private static final int COLOR_ACCENT = 0xFFFF9500;
private static final int COLOR_TOKEN_BG = 0xFF3A3A3A;
private static final int COLOR_BUTTON_POSITIVE = 0xFFFF9500;
private static final int COLOR_BUTTON_NEGATIVE = 0xFFFF6B6B;
// Match standalone K1: k1:, K1:, k1>, etc.
private static final Pattern K1_STANDALONE_PATTERN = Pattern.compile("(?i)(?:k1\\s*[:>]\\s*)([0-9a-f]{32})");
// Match combined r3+k1: format (64 chars = r3(32) + k1(32))
private static final Pattern K1_COMBINED_PATTERN = Pattern.compile("(?i)r3\\+k1\\s*:\\s*([0-9a-f]{64})");
private static volatile boolean k1Logged = false;
private static volatile boolean lifecycleCallbacksRegistered = false;
private static Context appContext;
/**
* Get K1 tokens from database and log files.
* Call this after the app initializes.
*
* @param context Application context
*/
public static void showK1Tokens(Context context) {
if (k1Logged) {
return;
}
appContext = context.getApplicationContext();
Set<String> allTokens = new LinkedHashSet<>();
// First try to get from database.
String dbToken = getK1TokensFromDatabase();
if (dbToken != null) {
allTokens.add(dbToken);
}
// Then get from log files.
Set<String> logTokens = getK1TokensFromLogFiles();
allTokens.addAll(logTokens);
if (allTokens.isEmpty()) {
return;
}
// Log all found tokens.
int index = 1;
for (String token : allTokens) {
Log.i(TAG, "#" + index++ + ": " + token.toUpperCase());
}
// Register lifecycle callbacks to show dialog when an Activity is ready.
registerLifecycleCallbacks(allTokens);
k1Logged = true;
}
/**
* Register ActivityLifecycleCallbacks to show dialog when first Activity resumes.
*
* @param tokens Set of K1 tokens to display
*/
private static void registerLifecycleCallbacks(Set<String> tokens) {
if (lifecycleCallbacksRegistered || !(appContext instanceof Application)) {
return;
}
Application application = (Application) appContext;
application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override
public void onActivityStarted(Activity activity) {
}
@Override
public void onActivityResumed(Activity activity) {
// Check if user chose not to show dialog.
SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
if (prefs.getBoolean(KEY_DONT_SHOW_DIALOG, false)) {
application.unregisterActivityLifecycleCallbacks(this);
lifecycleCallbacksRegistered = false;
return;
}
// Show dialog on first Activity resume.
if (tokens != null && !tokens.isEmpty()) {
activity.runOnUiThread(() -> showK1TokensDialog(activity, tokens));
// Unregister after showing
application.unregisterActivityLifecycleCallbacks(this);
lifecycleCallbacksRegistered = false;
}
}
@Override
public void onActivityPaused(Activity activity) {
}
@Override
public void onActivityStopped(Activity activity) {
}
@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override
public void onActivityDestroyed(Activity activity) {
}
});
lifecycleCallbacksRegistered = true;
}
/**
* Show dialog with K1 tokens.
*
* @param activity Activity context
* @param tokens Set of K1 tokens
*/
private static void showK1TokensDialog(Activity activity, Set<String> tokens) {
try {
// Create main container.
LinearLayout mainLayout = new LinearLayout(activity);
mainLayout.setOrientation(LinearLayout.VERTICAL);
mainLayout.setBackgroundColor(COLOR_BG);
mainLayout.setPadding(dpToPx(activity, 24), dpToPx(activity, 16),
dpToPx(activity, 24), dpToPx(activity, 16));
// Title.
TextView titleView = new TextView(activity);
titleView.setText("K1 Token(s) Found");
titleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
titleView.setTypeface(Typeface.DEFAULT_BOLD);
titleView.setTextColor(COLOR_TEXT_PRIMARY);
titleView.setGravity(Gravity.CENTER);
mainLayout.addView(titleView, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
// Subtitle.
TextView subtitleView = new TextView(activity);
subtitleView.setText(tokens.size() == 1 ? "1 token found • Tap to copy" : tokens.size() + " tokens found • Tap to copy");
subtitleView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
subtitleView.setTextColor(COLOR_TEXT_SECONDARY);
subtitleView.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams subtitleParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
subtitleParams.topMargin = dpToPx(activity, 4);
subtitleParams.bottomMargin = dpToPx(activity, 16);
mainLayout.addView(subtitleView, subtitleParams);
// Scrollable content.
ScrollView scrollView = new ScrollView(activity);
scrollView.setVerticalScrollBarEnabled(false);
LinearLayout.LayoutParams scrollParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
0,
1.0f
);
scrollParams.topMargin = dpToPx(activity, 8);
scrollParams.bottomMargin = dpToPx(activity, 16);
mainLayout.addView(scrollView, scrollParams);
LinearLayout tokensContainer = new LinearLayout(activity);
tokensContainer.setOrientation(LinearLayout.VERTICAL);
scrollView.addView(tokensContainer);
// Add each token as a card.
boolean singleToken = tokens.size() == 1;
int index = 1;
for (String token : tokens) {
LinearLayout tokenCard = createTokenCard(activity, token, index++, singleToken);
LinearLayout.LayoutParams cardParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
cardParams.bottomMargin = dpToPx(activity, 12);
tokensContainer.addView(tokenCard, cardParams);
}
// Info text.
TextView infoView = new TextView(activity);
infoView.setText(tokens.size() == 1 ? "Tap the token to copy it" : "Tap any token to copy it");
infoView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
infoView.setTextColor(COLOR_TEXT_SECONDARY);
infoView.setGravity(Gravity.CENTER);
infoView.setAlpha(0.7f);
LinearLayout.LayoutParams infoParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
infoParams.topMargin = dpToPx(activity, 8);
mainLayout.addView(infoView, infoParams);
// Button row.
LinearLayout buttonRow = new LinearLayout(activity);
buttonRow.setOrientation(LinearLayout.HORIZONTAL);
buttonRow.setGravity(Gravity.END);
LinearLayout.LayoutParams buttonRowParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
buttonRowParams.topMargin = dpToPx(activity, 16);
mainLayout.addView(buttonRow, buttonRowParams);
// "Don't show again" button.
Button dontShowButton = new Button(activity);
dontShowButton.setText("Don't show again");
dontShowButton.setTextColor(Color.WHITE);
dontShowButton.setBackgroundColor(Color.TRANSPARENT);
dontShowButton.setAllCaps(false);
dontShowButton.setTypeface(Typeface.DEFAULT);
dontShowButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
dontShowButton.setPadding(dpToPx(activity, 16), dpToPx(activity, 8),
dpToPx(activity, 16), dpToPx(activity, 8));
LinearLayout.LayoutParams dontShowParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
dontShowParams.rightMargin = dpToPx(activity, 8);
buttonRow.addView(dontShowButton, dontShowParams);
// "OK" button.
Button okButton = new Button(activity);
okButton.setText("OK");
okButton.setTextColor(Color.BLACK);
okButton.setBackgroundColor(COLOR_BUTTON_POSITIVE);
okButton.setAllCaps(false);
okButton.setTypeface(Typeface.DEFAULT_BOLD);
okButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
okButton.setPadding(dpToPx(activity, 24), dpToPx(activity, 12),
dpToPx(activity, 24), dpToPx(activity, 12));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
okButton.setElevation(dpToPx(activity, 4));
}
buttonRow.addView(okButton, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
));
// Build dialog.
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setView(mainLayout);
final AlertDialog dialog = builder.create();
// Style the dialog with dark background.
if (dialog.getWindow() != null) {
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
}
dialog.show();
// Set button click listeners after dialog is created.
dontShowButton.setOnClickListener(v -> {
SharedPreferences prefs = activity.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, true).apply();
Toast.makeText(activity, "Dialog disabled. Clear app data to re-enable.",
Toast.LENGTH_SHORT).show();
dialog.dismiss();
});
okButton.setOnClickListener(v -> {
dialog.dismiss();
});
} catch (Exception e) {
Log.e(TAG, "Failed to show K1 dialog", e);
}
}
/**
* Create a card view for a single token.
*/
private static LinearLayout createTokenCard(Activity activity, String token, int index, boolean singleToken) {
LinearLayout card = new LinearLayout(activity);
card.setOrientation(LinearLayout.VERTICAL);
card.setBackgroundColor(COLOR_TOKEN_BG);
card.setPadding(dpToPx(activity, 16), dpToPx(activity, 12),
dpToPx(activity, 16), dpToPx(activity, 12));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
card.setElevation(dpToPx(activity, 2));
}
card.setClickable(true);
card.setFocusable(true);
// Token label (only show if multiple tokens).
if (!singleToken) {
TextView labelView = new TextView(activity);
labelView.setText("Token #" + index);
labelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12);
labelView.setTextColor(COLOR_ACCENT);
labelView.setTypeface(Typeface.DEFAULT_BOLD);
card.addView(labelView);
}
// Token value.
TextView tokenView = new TextView(activity);
tokenView.setText(token.toUpperCase());
tokenView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
tokenView.setTextColor(COLOR_TEXT_PRIMARY);
tokenView.setTypeface(Typeface.MONOSPACE);
tokenView.setLetterSpacing(0.05f);
LinearLayout.LayoutParams tokenParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
);
if (!singleToken) {
tokenParams.topMargin = dpToPx(activity, 8);
}
card.addView(tokenView, tokenParams);
// Click to copy.
card.setOnClickListener(v -> {
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
if (clipboard != null) {
clipboard.setText(token.toUpperCase());
Toast.makeText(activity, "Token copied!", Toast.LENGTH_SHORT).show();
}
});
return card;
}
/**
* Convert dp to pixels.
*/
private static int dpToPx(Context context, float dp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
context.getResources().getDisplayMetrics()
);
}
/**
* Get K1 tokens from log files.
* Prioritizes pairing K1 tokens over reconnect tokens.
*/
private static Set<String> getK1TokensFromLogFiles() {
Set<String> pairingTokens = new LinkedHashSet<>();
Set<String> reconnectTokens = new LinkedHashSet<>();
try {
File logDir = new File("/data/data/" + PACKAGE_NAME + "/files/log");
if (!logDir.exists() || !logDir.isDirectory()) {
return pairingTokens;
}
File[] logFiles = logDir.listFiles((dir, name) ->
name.endsWith(".log") || name.endsWith(".log.") || name.matches(".*\\.log\\.\\d+"));
if (logFiles == null || logFiles.length == 0) {
return pairingTokens;
}
for (File logFile : logFiles) {
try (BufferedReader reader = new BufferedReader(new FileReader(logFile))) {
String line;
while ((line = reader.readLine()) != null) {
// Determine if this is a pairing or reconnect context.
boolean isPairingContext = line.toLowerCase().contains("watchbind");
boolean isReconnectContext = line.toLowerCase().contains("watchreconnect");
String k1Token = null;
// First check for combined r3+k1 format (priority).
Matcher combinedMatcher = K1_COMBINED_PATTERN.matcher(line);
if (combinedMatcher.find()) {
String combined = combinedMatcher.group(1);
if (combined.length() == 64) {
// Second half is the actual K1
k1Token = combined.substring(32).toLowerCase();
}
}
// Then check for standalone K1 format (only if not found in combined).
if (k1Token == null) {
Matcher standaloneMatcher = K1_STANDALONE_PATTERN.matcher(line);
if (standaloneMatcher.find()) {
String token = standaloneMatcher.group(1);
if (token != null && token.length() == 32) {
k1Token = token.toLowerCase();
}
}
}
// Add to appropriate set.
if (k1Token != null) {
if (isPairingContext && !isReconnectContext) {
pairingTokens.add(k1Token);
} else {
reconnectTokens.add(k1Token);
}
}
}
} catch (Exception e) {
// Skip unreadable files.
}
}
} catch (Exception ex) {
// Fail silently.
}
// Return pairing tokens first, add reconnect tokens if no pairing tokens found.
if (!pairingTokens.isEmpty()) {
Log.i(TAG, "Found " + pairingTokens.size() + " pairing K1 token(s)");
return pairingTokens;
}
if (!reconnectTokens.isEmpty()) {
Log.i(TAG, "Found " + reconnectTokens.size() + " reconnect K1 token(s) (may not work for initial pairing)");
}
return reconnectTokens;
}
/**
* Try to get K1 tokens from the database.
*/
private static String getK1TokensFromDatabase() {
try {
File dbDir = new File("/data/data/" + PACKAGE_NAME + "/databases");
if (!dbDir.exists() || !dbDir.isDirectory()) {
return null;
}
File[] dbFiles = dbDir.listFiles((dir, name) ->
name.endsWith(".db") && !name.startsWith("google_app_measurement") && !name.contains("firebase"));
if (dbFiles == null || dbFiles.length == 0) {
return null;
}
for (File dbFile : dbFiles) {
String token = getK1TokensFromDatabase(dbFile);
if (token != null) {
return token;
}
}
return null;
} catch (Exception ex) {
return null;
}
}
/**
* Extract K1 tokens from a database file.
*/
private static String getK1TokensFromDatabase(File dbFile) {
SQLiteDatabase db = null;
try {
db = SQLiteDatabase.openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READONLY);
// Get all tables.
Cursor cursor = db.rawQuery(
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'",
null
);
List<String> tables = new ArrayList<>();
while (cursor.moveToNext()) {
tables.add(cursor.getString(0));
}
cursor.close();
// Scan all columns for 32-char hex strings.
for (String table : tables) {
Cursor schemaCursor = null;
try {
schemaCursor = db.rawQuery("PRAGMA table_info(" + table + ")", null);
List<String> columns = new ArrayList<>();
while (schemaCursor.moveToNext()) {
columns.add(schemaCursor.getString(1));
}
schemaCursor.close();
for (String column : columns) {
Cursor dataCursor = null;
try {
dataCursor = db.query(table, new String[]{column}, null, null, null, null, null);
while (dataCursor.moveToNext()) {
String value = dataCursor.getString(0);
if (value != null && value.length() == 32 && value.matches("[0-9a-fA-F]{32}")) {
// Skip obviously fake tokens (MD5 of empty string).
if (!value.equalsIgnoreCase(EMPTY_MD5)) {
dataCursor.close();
db.close();
return value.toLowerCase();
}
}
}
} catch (Exception e) {
// Skip non-string columns.
} finally {
if (dataCursor != null) {
dataCursor.close();
}
}
}
} catch (Exception e) {
// Continue to next table.
} finally {
if (schemaCursor != null && !schemaCursor.isClosed()) {
schemaCursor.close();
}
}
}
return null;
} catch (Exception ex) {
return null;
} finally {
if (db != null && db.isOpen()) {
db.close();
}
}
}
/**
* Reset the logged flag (useful for testing or re-pairing).
*/
public static void resetK1Logged() {
k1Logged = false;
lifecycleCallbacksRegistered = false;
}
/**
* Reset the "don't show again" preference.
*/
public static void resetDontShowPreference() {
if (appContext != null) {
SharedPreferences prefs = appContext.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
prefs.edit().putBoolean(KEY_DONT_SHOW_DIALOG, false).apply();
}
}
}

View File

@@ -0,0 +1,17 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -16,6 +16,7 @@ import java.util.Arrays;
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.ui.Dim;
import com.amazon.video.sdk.player.Player; import com.amazon.video.sdk.player.Player;
@@ -64,9 +65,8 @@ public class PlaybackSpeedPatch {
SpeedIconDrawable speedIcon = new SpeedIconDrawable(); SpeedIconDrawable speedIcon = new SpeedIconDrawable();
speedButton.setImageDrawable(speedIcon); speedButton.setImageDrawable(speedIcon);
int buttonSize = Utils.dipToPixels(48); speedButton.setMinimumWidth(Dim.dp48);
speedButton.setMinimumWidth(buttonSize); speedButton.setMinimumHeight(Dim.dp48);
speedButton.setMinimumHeight(buttonSize);
return speedButton; return speedButton;
} }
@@ -197,11 +197,11 @@ class SpeedIconDrawable extends Drawable {
@Override @Override
public int getIntrinsicWidth() { public int getIntrinsicWidth() {
return Utils.dipToPixels(32); return Dim.dp32;
} }
@Override @Override
public int getIntrinsicHeight() { public int getIntrinsicHeight() {
return Utils.dipToPixels(32); return Dim.dp32;
} }
} }

View File

@@ -0,0 +1,57 @@
package app.revanced.extension.shared;
import java.util.HashMap;
import java.util.Map;
public enum ResourceType {
ANIM("anim"),
ANIMATOR("animator"),
ARRAY("array"),
ATTR("attr"),
BOOL("bool"),
COLOR("color"),
DIMEN("dimen"),
DRAWABLE("drawable"),
FONT("font"),
FRACTION("fraction"),
ID("id"),
INTEGER("integer"),
INTERPOLATOR("interpolator"),
LAYOUT("layout"),
MENU("menu"),
MIPMAP("mipmap"),
NAVIGATION("navigation"),
PLURALS("plurals"),
RAW("raw"),
STRING("string"),
STYLE("style"),
STYLEABLE("styleable"),
TRANSITION("transition"),
VALUES("values"),
XML("xml");
private static final Map<String, ResourceType> VALUE_MAP;
static {
ResourceType[] values = values();
VALUE_MAP = new HashMap<>(2 * values.length);
for (ResourceType type : values) {
VALUE_MAP.put(type.value, type);
}
}
public final String value;
public static ResourceType fromValue(String value) {
ResourceType type = VALUE_MAP.get(value);
if (type == null) {
throw new IllegalArgumentException("Unknown resource type: " + value);
}
return type;
}
ResourceType(String value) {
this.value = value;
}
}

View File

@@ -23,9 +23,7 @@ import android.os.Looper;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceGroup; import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.util.DisplayMetrics;
import android.util.Pair; import android.util.Pair;
import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@@ -45,10 +43,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.text.Bidi; import java.text.Bidi;
import java.text.Collator;
import java.text.Normalizer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@@ -61,6 +63,7 @@ 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.BooleanSetting; import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference; import app.revanced.extension.shared.settings.preference.ReVancedAboutPreference;
import app.revanced.extension.shared.ui.Dim;
@SuppressWarnings("NewApi") @SuppressWarnings("NewApi")
public class Utils { public class Utils {
@@ -79,6 +82,17 @@ public class Utils {
@Nullable @Nullable
private static Boolean isDarkModeEnabled; private static Boolean isDarkModeEnabled;
private static boolean appIsUsingBoldIcons;
// Cached Collator instance with its locale.
@Nullable
private static Locale cachedCollatorLocale;
@Nullable
private static Collator cachedCollator;
private static final Pattern PUNCTUATION_PATTERN = Pattern.compile("\\p{P}+");
private static final Pattern DIACRITICS_PATTERN = Pattern.compile("\\p{M}");
private Utils() { private Utils() {
} // utility class } // utility class
@@ -142,12 +156,12 @@ public class Utils {
/** /**
* Hide a view by setting its layout height and width to 1dp. * Hide a view by setting its layout height and width to 1dp.
* *
* @param condition The setting to check for hiding the view. * @param setting The setting to check for hiding the view.
* @param view The view to hide. * @param view The view to hide.
*/ */
public static void hideViewBy0dpUnderCondition(BooleanSetting condition, View view) { public static void hideViewBy0dpUnderCondition(BooleanSetting setting, View view) {
if (hideViewBy0dpUnderCondition(condition.get(), view)) { if (hideViewBy0dpUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition); Logger.printDebug(() -> "View hidden by setting: " + setting);
} }
} }
@@ -159,22 +173,47 @@ public class Utils {
*/ */
public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) { public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) {
if (condition) { if (condition) {
hideViewByLayoutParams(view); hideViewBy0dp(view);
return true; return true;
} }
return false; return false;
} }
/**
* Hide a view by setting its layout params to 0x0
* @param view The view to hide.
*/
public static void hideViewBy0dp(View view) {
if (view instanceof LinearLayout) {
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams);
} else if (view instanceof FrameLayout) {
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams2);
} else if (view instanceof RelativeLayout) {
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams3);
} else if (view instanceof Toolbar) {
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0);
view.setLayoutParams(layoutParams4);
} else {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = 0;
params.height = 0;
view.setLayoutParams(params);
}
}
/** /**
* Hide a view by setting its visibility to GONE. * Hide a view by setting its visibility to GONE.
* *
* @param condition The setting to check for hiding the view. * @param setting The setting to check for hiding the view.
* @param view The view to hide. * @param view The view to hide.
*/ */
public static void hideViewUnderCondition(BooleanSetting condition, View view) { public static void hideViewUnderCondition(BooleanSetting setting, View view) {
if (hideViewUnderCondition(condition.get(), view)) { if (hideViewUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition); Logger.printDebug(() -> "View hidden by setting: " + setting);
} }
} }
@@ -193,14 +232,14 @@ public class Utils {
return false; return false;
} }
public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting condition, View view) { public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting setting, View view) {
if (hideViewByRemovingFromParentUnderCondition(condition.get(), view)) { if (hideViewByRemovingFromParentUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition); Logger.printDebug(() -> "View hidden by setting: " + setting);
} }
} }
public static boolean hideViewByRemovingFromParentUnderCondition(boolean setting, View view) { public static boolean hideViewByRemovingFromParentUnderCondition(boolean condition, View view) {
if (setting) { if (condition) {
ViewParent parent = view.getParent(); ViewParent parent = view.getParent();
if (parent instanceof ViewGroup parentGroup) { if (parent instanceof ViewGroup parentGroup) {
parentGroup.removeView(view); parentGroup.removeView(view);
@@ -272,12 +311,13 @@ public class Utils {
* @return zero, if the resource is not found. * @return zero, if the resource is not found.
*/ */
@SuppressLint("DiscouragedApi") @SuppressLint("DiscouragedApi")
public static int getResourceIdentifier(Context context, String resourceIdentifierName, @Nullable String type) { public static int getResourceIdentifier(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName()); return context.getResources().getIdentifier(resourceIdentifierName,
type == null ? null : type.value, context.getPackageName());
} }
public static int getResourceIdentifierOrThrow(Context context, String resourceIdentifierName, @Nullable String type) { public static int getResourceIdentifierOrThrow(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
final int resourceId = getResourceIdentifier(context, resourceIdentifierName, type); final int resourceId = getResourceIdentifier(context, type, resourceIdentifierName);
if (resourceId == 0) { if (resourceId == 0) {
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
+ " type: " + type); + " type: " + type);
@@ -287,48 +327,48 @@ public class Utils {
/** /**
* @return zero, if the resource is not found. * @return zero, if the resource is not found.
* @see #getResourceIdentifierOrThrow(String, String) * @see #getResourceIdentifierOrThrow(ResourceType, String)
*/ */
public static int getResourceIdentifier(String resourceIdentifierName, @Nullable String type) { public static int getResourceIdentifier(@Nullable ResourceType type, String resourceIdentifierName) {
return getResourceIdentifier(getContext(), resourceIdentifierName, type); return getResourceIdentifier(getContext(), type, resourceIdentifierName);
} }
/** /**
* @return The resource identifier, or throws an exception if not found. * @return zero, if the resource is not found.
* @see #getResourceIdentifier(ResourceType, String)
*/ */
public static int getResourceIdentifierOrThrow(String resourceIdentifierName, @Nullable String type) { public static int getResourceIdentifierOrThrow(@Nullable ResourceType type, String resourceIdentifierName) {
final int resourceId = getResourceIdentifier(getContext(), resourceIdentifierName, type); return getResourceIdentifierOrThrow(getContext(), type, resourceIdentifierName);
if (resourceId == 0) { }
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
+ " type: " + type); public static String getResourceString(int id) throws Resources.NotFoundException {
} return getContext().getResources().getString(id);
return resourceId;
} }
public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException { public static int getResourceInteger(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getInteger(getResourceIdentifierOrThrow(resourceIdentifierName, "integer")); return getContext().getResources().getInteger(getResourceIdentifierOrThrow(ResourceType.INTEGER, resourceIdentifierName));
} }
public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException { public static Animation getResourceAnimation(String resourceIdentifierName) throws Resources.NotFoundException {
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(resourceIdentifierName, "anim")); return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(ResourceType.ANIM, resourceIdentifierName));
} }
@ColorInt @ColorInt
public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException { public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException {
//noinspection deprecation //noinspection deprecation
return getContext().getResources().getColor(getResourceIdentifierOrThrow(resourceIdentifierName, "color")); return getContext().getResources().getColor(getResourceIdentifierOrThrow(ResourceType.COLOR, resourceIdentifierName));
} }
public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException { public static int getResourceDimensionPixelSize(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen")); return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
} }
public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException { public static float getResourceDimension(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getDimension(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen")); return getContext().getResources().getDimension(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
} }
public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException { public static String[] getResourceStringArray(String resourceIdentifierName) throws Resources.NotFoundException {
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(resourceIdentifierName, "array")); return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(ResourceType.ARRAY, resourceIdentifierName));
} }
public interface MatchFilter<T> { public interface MatchFilter<T> {
@@ -339,7 +379,7 @@ public class Utils {
* Includes sub children. * Includes sub children.
*/ */
public static <R extends View> R getChildViewByResourceName(View view, String str) { public static <R extends View> R getChildViewByResourceName(View view, String str) {
var child = view.findViewById(Utils.getResourceIdentifierOrThrow(str, "id")); var child = view.findViewById(Utils.getResourceIdentifierOrThrow(ResourceType.ID, str));
//noinspection unchecked //noinspection unchecked
return (R) child; return (R) child;
} }
@@ -749,31 +789,25 @@ public class Utils {
} }
/** /**
* Hide a view by setting its layout params to 0x0 * Hides a view by setting its layout width and height to 0dp.
* @param view The view to hide. * Handles null layout params safely.
*
* @param view The view to hide. If null, does nothing.
*/ */
public static void hideViewByLayoutParams(View view) { public static void hideViewByLayoutParams(@Nullable View view) {
if (view instanceof LinearLayout) { if (view == null) return;
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams); ViewGroup.LayoutParams params = view.getLayoutParams();
} else if (view instanceof FrameLayout) {
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0); if (params == null) {
view.setLayoutParams(layoutParams2); // Create generic 0x0 layout params accepted by all ViewGroups.
} else if (view instanceof RelativeLayout) { params = new ViewGroup.LayoutParams(0, 0);
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0);
view.setLayoutParams(layoutParams3);
} else if (view instanceof Toolbar) {
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0);
view.setLayoutParams(layoutParams4);
} else if (view instanceof ViewGroup) {
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(0, 0);
view.setLayoutParams(layoutParams5);
} else { } else {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = 0; params.width = 0;
params.height = 0; params.height = 0;
view.setLayoutParams(params);
} }
view.setLayoutParams(params);
} }
/** /**
@@ -790,13 +824,10 @@ public class Utils {
public static void setDialogWindowParameters(Window window, int gravity, int yOffsetDip, int widthPercentage, boolean dimAmount) { public static void setDialogWindowParameters(Window window, int gravity, int yOffsetDip, int widthPercentage, boolean dimAmount) {
WindowManager.LayoutParams params = window.getAttributes(); WindowManager.LayoutParams params = window.getAttributes();
DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); params.width = Dim.pctPortraitWidth(widthPercentage);
int portraitWidth = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels);
params.width = (int) (portraitWidth * (widthPercentage / 100.0f)); // Set width based on parameters.
params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = gravity; params.gravity = gravity;
params.y = yOffsetDip > 0 ? dipToPixels(yOffsetDip) : 0; params.y = yOffsetDip > 0 ? Dim.dp(yOffsetDip) : 0;
if (dimAmount) { if (dimAmount) {
params.dimAmount = 0f; params.dimAmount = 0f;
} }
@@ -806,15 +837,18 @@ public class Utils {
} }
/** /**
* Creates an array of corner radii for a rounded rectangle shape. * @return If the unpatched app is currently using bold icons.
*
* @param dp Radius in density-independent pixels (dip) to apply to all corners.
* @return An array of eight float values representing the corner radii
* (top-left, top-right, bottom-right, bottom-left).
*/ */
public static float[] createCornerRadii(float dp) { public static boolean appIsUsingBoldIcons() {
final float radius = dipToPixels(dp); return appIsUsingBoldIcons;
return new float[]{radius, radius, radius, radius, radius, radius, radius, radius}; }
/**
* Controls if ReVanced bold icons are shown in various places.
* @param boldIcons If the app is currently using bold icons.
*/
public static void setAppIsUsingBoldIcons(boolean boldIcons) {
appIsUsingBoldIcons = boldIcons;
} }
/** /**
@@ -976,30 +1010,60 @@ public class Utils {
} }
} }
private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+");
/** /**
* Strips all punctuation and converts to lower case. A null parameter returns an empty string. * Removes punctuation and converts text to lowercase. Returns an empty string if input is null.
*/ */
public static String removePunctuationToLowercase(@Nullable CharSequence original) { public static String removePunctuationToLowercase(@Nullable CharSequence original) {
if (original == null) return ""; if (original == null) return "";
return punctuationPattern.matcher(original).replaceAll("") return PUNCTUATION_PATTERN.matcher(original).replaceAll("")
.toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale()); .toLowerCase(BaseSettings.REVANCED_LANGUAGE.get().getLocale());
} }
/** /**
* Sort a PreferenceGroup and all it's sub groups by title or key. * Normalizes text for search: applies NFD, removes diacritics, and lowercases (locale-neutral).
* Returns an empty string if input is null.
*/
public static String normalizeTextToLowercase(@Nullable CharSequence original) {
if (original == null) return "";
return DIACRITICS_PATTERN.matcher(Normalizer.normalize(original, Normalizer.Form.NFD))
.replaceAll("").toLowerCase(Locale.ROOT);
}
/**
* Returns a cached Collator for the current locale, or creates a new one if locale changed.
*/
private static Collator getCollator() {
Locale currentLocale = BaseSettings.REVANCED_LANGUAGE.get().getLocale();
if (cachedCollator == null || !currentLocale.equals(cachedCollatorLocale)) {
cachedCollatorLocale = currentLocale;
cachedCollator = Collator.getInstance(currentLocale);
cachedCollator.setStrength(Collator.SECONDARY); // Case-insensitive, diacritic-insensitive.
}
return cachedCollator;
}
/**
* Sorts a {@link PreferenceGroup} and all nested subgroups by title or key.
* <p> * <p>
* Sort order is determined by the preferences key {@link Sort} suffix. * The sort order is controlled by the {@link Sort} suffix present in the preference key.
* Preferences without a key or without a {@link Sort} suffix remain in their original order.
* <p> * <p>
* If a preference has no key or no {@link Sort} suffix, * Sorting is performed using {@link Collator} with the current user locale,
* then the preferences are left unsorted. * ensuring correct alphabetical ordering for all supported languages
* (e.g., Ukrainian "і", German "ß", French accented characters, etc.).
*
* @param group the {@link PreferenceGroup} to sort
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public static void sortPreferenceGroups(PreferenceGroup group) { public static void sortPreferenceGroups(PreferenceGroup group) {
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED); Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
List<Pair<String, Preference>> preferences = new ArrayList<>(); List<Pair<String, Preference>> preferences = new ArrayList<>();
// Get cached Collator for locale-aware string comparison.
Collator collator = getCollator();
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) { for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
Preference preference = group.getPreference(i); Preference preference = group.getPreference(i);
@@ -1030,10 +1094,11 @@ public class Utils {
preferences.add(new Pair<>(sortValue, preference)); preferences.add(new Pair<>(sortValue, preference));
} }
//noinspection ComparatorCombinators // Sort the list using locale-specific collation rules.
Collections.sort(preferences, (pair1, pair2) Collections.sort(preferences, (pair1, pair2)
-> pair1.first.compareTo(pair2.first)); -> collator.compare(pair1.first, pair2.first));
// Reassign order values to reflect the new sorted sequence
int index = 0; int index = 0;
for (Pair<String, Preference> pair : preferences) { for (Pair<String, Preference> pair : preferences) {
int order = index++; int order = index++;
@@ -1090,42 +1155,6 @@ public class Utils {
return getResourceColor(colorString); return getResourceColor(colorString);
} }
/**
* Converts dip value to actual device pixels.
*
* @param dip The density-independent pixels value.
* @return The device pixel value.
*/
public static int dipToPixels(float dip) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dip,
Resources.getSystem().getDisplayMetrics()
);
}
/**
* Converts a percentage of the screen height to actual device pixels.
*
* @param percentage The percentage of the screen height (e.g., 30 for 30%).
* @return The device pixel value corresponding to the percentage of screen height.
*/
public static int percentageHeightToPixels(int percentage) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (metrics.heightPixels * (percentage / 100.0f));
}
/**
* Converts a percentage of the screen width to actual device pixels.
*
* @param percentage The percentage of the screen width (e.g., 30 for 30%).
* @return The device pixel value corresponding to the percentage of screen width.
*/
public static int percentageWidthToPixels(int percentage) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return (int) (metrics.widthPixels * (percentage / 100.0f));
}
/** /**
* Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active. * Uses {@link #adjustColorBrightness(int, float)} depending if light or dark mode is active.
*/ */
@@ -1183,4 +1212,18 @@ public class Utils {
public static float clamp(float value, float lower, float upper) { public static float clamp(float value, float lower, float upper) {
return Math.max(lower, Math.min(value, upper)); return Math.max(lower, Math.min(value, upper));
} }
/**
* @param maxSize The maximum number of elements to keep in the map.
* @return A {@link LinkedHashMap} that automatically evicts the oldest entry
* when the size exceeds {@code maxSize}.
*/
public static <T, V> Map<T, V> createSizeRestrictedMap(int maxSize) {
return new LinkedHashMap<>(2 * maxSize) {
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > maxSize;
}
};
}
} }

View File

@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import java.util.Collection; import java.util.Collection;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.ui.CustomDialog; import app.revanced.extension.shared.ui.CustomDialog;
@@ -128,7 +129,7 @@ abstract class Check {
// Add icon to the dialog. // Add icon to the dialog.
ImageView iconView = new ImageView(activity); ImageView iconView = new ImageView(activity);
iconView.setImageResource(Utils.getResourceIdentifierOrThrow( iconView.setImageResource(Utils.getResourceIdentifierOrThrow(
"revanced_ic_dialog_alert", "drawable")); ResourceType.DRAWABLE, "revanced_ic_dialog_alert"));
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN); iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
iconView.setPadding(0, 0, 0, 0); iconView.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(

View File

@@ -15,7 +15,6 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
public abstract class BaseFixRedgifsApiPatch implements Interceptor { public abstract class BaseFixRedgifsApiPatch implements Interceptor {
protected static BaseFixRedgifsApiPatch INSTANCE; protected static BaseFixRedgifsApiPatch INSTANCE;
public abstract String getDefaultUserAgent(); public abstract String getDefaultUserAgent();

View File

@@ -61,7 +61,11 @@ public class CheckWatchHistoryDomainNameResolutionPatch {
// Prevent this false positive by verify youtube.com resolves. // Prevent this false positive by verify youtube.com resolves.
// If youtube.com does not resolve, then it's not a watch history domain resolving error // If youtube.com does not resolve, then it's not a watch history domain resolving error
// because the entire app will not work since no domains are resolving. // because the entire app will not work since no domains are resolving.
if (!domainResolvesToValidIP("youtube.com") String domainYouTube = "youtube.com";
if (!domainResolvesToValidIP(domainYouTube)
|| domainResolvesToValidIP(HISTORY_TRACKING_ENDPOINT)
// Check multiple times, so a false positive from a flaky connection is almost impossible.
|| !domainResolvesToValidIP(domainYouTube)
|| domainResolvesToValidIP(HISTORY_TRACKING_ENDPOINT)) { || domainResolvesToValidIP(HISTORY_TRACKING_ENDPOINT)) {
return; return;
} }

View File

@@ -5,6 +5,7 @@ import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Color; import android.graphics.Color;
import android.view.View;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -12,6 +13,7 @@ import java.util.Locale;
import app.revanced.extension.shared.GmsCoreSupport; import app.revanced.extension.shared.GmsCoreSupport;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
@@ -64,13 +66,24 @@ public class CustomBrandingPatch {
iconName += "_custom"; iconName += "_custom";
} }
notificationSmallIcon = Utils.getResourceIdentifier(iconName, "drawable"); notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
if (notificationSmallIcon == 0) { if (notificationSmallIcon == 0) {
Logger.printException(() -> "Could not load notification small icon"); Logger.printException(() -> "Could not load notification small icon");
} }
} }
} }
/**
* Injection point.
*/
public static View getLottieViewOrNull(View lottieStartupView) {
if (BaseSettings.CUSTOM_BRANDING_ICON.get() == BrandingTheme.ORIGINAL) {
return lottieStartupView;
}
return null;
}
/** /**
* Injection point. * Injection point.
*/ */
@@ -150,14 +163,14 @@ public class CustomBrandingPatch {
} }
for (ComponentName disable : componentsToDisable) { for (ComponentName disable : componentsToDisable) {
// Use info logging because if the alias status become corrupt the app cannot launch.
Logger.printInfo(() -> "Disabling: " + disable.getClassName());
pm.setComponentEnabledSetting(disable, pm.setComponentEnabledSetting(disable,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
} }
// Use info logging because if the alias status become corrupt the app cannot launch.
ComponentName componentToEnableFinal = componentToEnable; ComponentName componentToEnableFinal = componentToEnable;
Logger.printInfo(() -> "Enabling: " + componentToEnableFinal.getClassName()); Logger.printInfo(() -> "Enabling: " + componentToEnableFinal.getClassName());
pm.setComponentEnabledSetting(componentToEnable, pm.setComponentEnabledSetting(componentToEnable,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
} catch (Exception ex) { } catch (Exception ex) {

View File

@@ -1,5 +1,9 @@
package app.revanced.extension.shared.patches; package app.revanced.extension.shared.patches;
import static java.lang.Boolean.TRUE;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@@ -21,12 +25,28 @@ public final class EnableDebuggingPatch {
? new ConcurrentHashMap<>(800, 0.5f, 1) ? new ConcurrentHashMap<>(800, 0.5f, 1)
: null; : null;
private static final Set<Long> DISABLED_FEATURE_FLAGS = parseFlags(BaseSettings.DISABLED_FEATURE_FLAGS.get());
// Log all disabled flags on app startup.
static {
if (LOG_FEATURE_FLAGS && !DISABLED_FEATURE_FLAGS.isEmpty()) {
StringBuilder sb = new StringBuilder("Disabled feature flags:\n");
for (Long flag : DISABLED_FEATURE_FLAGS) {
sb.append(" ").append(flag).append('\n');
}
Logger.printDebug(sb::toString);
}
}
/** /**
* Injection point. * Injection point.
*/ */
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) { public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
if (LOG_FEATURE_FLAGS && value) { if (LOG_FEATURE_FLAGS && value) {
if (featureFlags.putIfAbsent(flag, true) == null) { if (DISABLED_FEATURE_FLAGS.contains(flag)) {
return false;
}
if (featureFlags.putIfAbsent(flag, TRUE) == null) {
Logger.printDebug(() -> "boolean feature is enabled: " + flag); Logger.printDebug(() -> "boolean feature is enabled: " + flag);
} }
} }
@@ -70,10 +90,44 @@ public final class EnableDebuggingPatch {
if (LOG_FEATURE_FLAGS && !defaultValue.equals(value)) { if (LOG_FEATURE_FLAGS && !defaultValue.equals(value)) {
if (featureFlags.putIfAbsent(flag, true) == null) { if (featureFlags.putIfAbsent(flag, true) == null) {
Logger.printDebug(() -> " string feature is enabled: " + flag Logger.printDebug(() -> " string feature is enabled: " + flag
+ " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue)); + " value: " + value + (defaultValue.isEmpty() ? "" : " default: " + defaultValue));
} }
} }
return value; return value;
} }
/**
* Get all logged feature flags.
* @return Set of all known flags
*/
public static Set<Long> getAllLoggedFlags() {
if (featureFlags != null) {
return new HashSet<>(featureFlags.keySet());
}
return new HashSet<>();
}
/**
* Public method for parsing flags.
* @param flags String containing newline-separated flag IDs
* @return Set of parsed flag IDs
*/
public static Set<Long> parseFlags(String flags) {
Set<Long> parsedFlags = new HashSet<>();
if (!flags.isBlank()) {
for (String flag : flags.split("\n")) {
String trimmedFlag = flag.trim();
if (trimmedFlag.isEmpty()) continue; // Skip empty lines.
try {
parsedFlags.add(Long.parseLong(trimmedFlag));
} catch (NumberFormatException e) {
Logger.printException(() -> "Invalid flag ID: " + flag);
}
}
}
return parsedFlags;
}
} }

View File

@@ -1,4 +1,4 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.shared.patches.components;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
@@ -15,13 +15,15 @@ import java.util.regex.Pattern;
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.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.patches.litho.Filter;
/** /**
* Allows custom filtering using a path and optionally a proto buffer string. * Allows custom filtering using a path and optionally a proto buffer string.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
final class CustomFilter extends Filter { public final class CustomFilter extends Filter {
private static void showInvalidSyntaxToast(@NonNull String expression) { private static void showInvalidSyntaxToast(@NonNull String expression) {
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression)); Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
@@ -45,7 +47,7 @@ final class CustomFilter extends Filter {
@NonNull @NonNull
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
static Collection<CustomFilterGroup> parseCustomFilterGroups() { static Collection<CustomFilterGroup> parseCustomFilterGroups() {
String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get(); String rawCustomFilterText = YouTubeAndMusicSettings.CUSTOM_FILTER_STRINGS.get();
if (rawCustomFilterText.isBlank()) { if (rawCustomFilterText.isBlank()) {
return Collections.emptyList(); return Collections.emptyList();
} }
@@ -100,7 +102,7 @@ final class CustomFilter extends Filter {
ByteTrieSearch bufferSearch; ByteTrieSearch bufferSearch;
CustomFilterGroup(boolean startsWith, @NonNull String path) { CustomFilterGroup(boolean startsWith, @NonNull String path) {
super(Settings.CUSTOM_FILTER, path); super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
this.startsWith = startsWith; this.startsWith = startsWith;
} }
@@ -145,7 +147,7 @@ final class CustomFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// All callbacks are custom filter groups. // All callbacks are custom filter groups.
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup; CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
@@ -159,4 +161,4 @@ final class CustomFilter extends Filter {
return custom.bufferSearch.matches(buffer); return custom.bufferSearch.matches(buffer);
} }
} }

View File

@@ -1,9 +1,12 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.shared.patches.litho;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
/** /**
* Filters litho based components. * Filters litho based components.
* *
@@ -14,11 +17,11 @@ import java.util.List;
* either an identifier or a path. * either an identifier or a path.
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)} * Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
* search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern) * search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern)
* or a {@link ByteArrayFilterGroupList} (if searching for more than 1 pattern). * or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
* *
* All callbacks must be registered before the constructor completes. * All callbacks must be registered before the constructor completes.
*/ */
abstract class Filter { public abstract class Filter {
public enum FilterContentType { public enum FilterContentType {
IDENTIFIER, IDENTIFIER,
@@ -30,12 +33,12 @@ abstract class Filter {
* Identifier callbacks. Do not add to this instance, * Identifier callbacks. Do not add to this instance,
* and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}. * and instead use {@link #addIdentifierCallbacks(StringFilterGroup...)}.
*/ */
protected final List<StringFilterGroup> identifierCallbacks = new ArrayList<>(); public final List<StringFilterGroup> identifierCallbacks = new ArrayList<>();
/** /**
* Path callbacks. Do not add to this instance, * Path callbacks. Do not add to this instance,
* and instead use {@link #addPathCallbacks(StringFilterGroup...)}. * and instead use {@link #addPathCallbacks(StringFilterGroup...)}.
*/ */
protected final List<StringFilterGroup> pathCallbacks = new ArrayList<>(); public final List<StringFilterGroup> pathCallbacks = new ArrayList<>();
/** /**
* Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)} * Adds callbacks to {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)}
@@ -65,7 +68,7 @@ abstract class Filter {
* @param contentIndex Matched index of the identifier or path. * @param contentIndex Matched index of the identifier or path.
* @return True if the litho component should be filtered out. * @return True if the litho component should be filtered out.
*/ */
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
return true; return true;
} }

View File

@@ -0,0 +1,213 @@
package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BooleanSetting;
public abstract class FilterGroup<T> {
public final static class FilterGroupResult {
private BooleanSetting setting;
private int matchedIndex;
private int matchedLength;
// In the future it might be useful to include which pattern matched,
// but for now that is not needed.
FilterGroupResult() {
this(null, -1, 0);
}
FilterGroupResult(BooleanSetting setting, int matchedIndex, int matchedLength) {
setValues(setting, matchedIndex, matchedLength);
}
public void setValues(BooleanSetting setting, int matchedIndex, int matchedLength) {
this.setting = setting;
this.matchedIndex = matchedIndex;
this.matchedLength = matchedLength;
}
/**
* A null value if the group has no setting,
* or if no match is returned from {@link FilterGroupList#check(Object)}.
*/
public BooleanSetting getSetting() {
return setting;
}
public boolean isFiltered() {
return matchedIndex >= 0;
}
/**
* Matched index of first pattern that matched, or -1 if nothing matched.
*/
public int getMatchedIndex() {
return matchedIndex;
}
/**
* Length of the matched filter pattern.
*/
public int getMatchedLength() {
return matchedLength;
}
}
protected final BooleanSetting setting;
public final T[] filters;
/**
* Initialize a new filter group.
*
* @param setting The associated setting.
* @param filters The filters.
*/
@SafeVarargs
public FilterGroup(final BooleanSetting setting, final T... filters) {
this.setting = setting;
this.filters = filters;
if (filters.length == 0) {
throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)");
}
}
public boolean isEnabled() {
return setting == null || setting.get();
}
/**
* @return If {@link FilterGroupList} should include this group when searching.
* By default, all filters are included except non enabled settings that require reboot.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean includeInSearch() {
return isEnabled() || !setting.rebootApp;
}
@NonNull
@Override
public String toString() {
return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting);
}
public abstract FilterGroupResult check(final T stack);
public static class StringFilterGroup extends FilterGroup<String> {
public StringFilterGroup(final BooleanSetting setting, final String... filters) {
super(setting, filters);
}
@Override
public FilterGroupResult check(final String string) {
int matchedIndex = -1;
int matchedLength = 0;
if (isEnabled()) {
for (String pattern : filters) {
if (!string.isEmpty()) {
final int indexOf = string.indexOf(pattern);
if (indexOf >= 0) {
matchedIndex = indexOf;
matchedLength = pattern.length();
break;
}
}
}
}
return new FilterGroupResult(setting, matchedIndex, matchedLength);
}
}
/**
* If you have more than 1 filter patterns, then all instances of
* this class should filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])},
* which uses a prefix tree to give better performance.
*/
public static class ByteArrayFilterGroup extends FilterGroup<byte[]> {
private volatile int[][] failurePatterns;
// Modified implementation from https://stackoverflow.com/a/1507813
private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) {
// Finds the first occurrence of the pattern in the byte array using
// KMP matching algorithm.
int patternLength = pattern.length;
for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) {
while (j > 0 && pattern[j] != data[i]) {
j = failure[j - 1];
}
if (pattern[j] == data[i]) {
j++;
}
if (j == patternLength) {
return i - patternLength + 1;
}
}
return -1;
}
private static int[] createFailurePattern(byte[] pattern) {
// Computes the failure function using a boot-strapping process,
// where the pattern is matched against itself.
final int patternLength = pattern.length;
final int[] failure = new int[patternLength];
for (int i = 1, j = 0; i < patternLength; i++) {
while (j > 0 && pattern[j] != pattern[i]) {
j = failure[j - 1];
}
if (pattern[j] == pattern[i]) {
j++;
}
failure[i] = j;
}
return failure;
}
public ByteArrayFilterGroup(BooleanSetting setting, byte[]... filters) {
super(setting, filters);
}
/**
* Converts the Strings into byte arrays. Used to search for text in binary data.
*/
public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
super(setting, ByteTrieSearch.convertStringsToBytes(filters));
}
private synchronized void buildFailurePatterns() {
if (failurePatterns != null) return; // Thread race and another thread already initialized the search.
Logger.printDebug(() -> "Building failure array for: " + this);
int[][] failurePatterns = new int[filters.length][];
int i = 0;
for (byte[] pattern : filters) {
failurePatterns[i++] = createFailurePattern(pattern);
}
this.failurePatterns = failurePatterns; // Must set after initialization finishes.
}
@Override
public FilterGroupResult check(final byte[] bytes) {
int matchedLength = 0;
int matchedIndex = -1;
if (isEnabled()) {
int[][] failures = failurePatterns;
if (failures == null) {
buildFailurePatterns(); // Lazy load.
failures = failurePatterns;
}
for (int i = 0, length = filters.length; i < length; i++) {
byte[] filter = filters[i];
matchedIndex = indexOf(bytes, filter, failures[i]);
if (matchedIndex >= 0) {
matchedLength = filter.length;
break;
}
}
}
return new FilterGroupResult(setting, matchedIndex, matchedLength);
}
}
}

View File

@@ -1,21 +1,22 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.*; import java.util.*;
import java.util.function.Consumer;
import app.revanced.extension.shared.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.TrieSearch; import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> { public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
private final List<T> filterGroups = new ArrayList<>(); private final List<T> filterGroups = new ArrayList<>();
private final TrieSearch<V> search = createSearchGraph(); private final TrieSearch<V> search = createSearchGraph();
@SafeVarargs @SafeVarargs
protected final void addAll(final T... groups) { public final void addAll(final T... groups) {
filterGroups.addAll(Arrays.asList(groups)); filterGroups.addAll(Arrays.asList(groups));
for (T group : groups) { for (T group : groups) {
@@ -41,18 +42,7 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
return filterGroups.iterator(); return filterGroups.iterator();
} }
@Override public FilterGroup.FilterGroupResult check(V stack) {
public void forEach(@NonNull Consumer<? super T> action) {
filterGroups.forEach(action);
}
@NonNull
@Override
public Spliterator<T> spliterator() {
return filterGroups.spliterator();
}
protected FilterGroup.FilterGroupResult check(V stack) {
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult(); FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
search.matches(stack, result); search.matches(stack, result);
return result; return result;
@@ -60,21 +50,21 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
} }
protected abstract TrieSearch<V> createSearchGraph(); protected abstract TrieSearch<V> createSearchGraph();
}
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> { public static final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
protected StringTrieSearch createSearchGraph() { protected StringTrieSearch createSearchGraph() {
return new StringTrieSearch(); return new StringTrieSearch();
}
} }
}
/** /**
* If searching for a single byte pattern, then it is slightly better to use * If searching for a single byte pattern, then it is slightly better to use
* {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster * {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
* than a prefix tree to search for only 1 pattern. * than a prefix tree to search for only 1 pattern.
*/ */
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> { public static final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
protected ByteTrieSearch createSearchGraph() { protected ByteTrieSearch createSearchGraph() {
return new ByteTrieSearch(); return new ByteTrieSearch();
}
} }
} }

View File

@@ -0,0 +1,252 @@
package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
@SuppressWarnings("unused")
public final class LithoFilterPatch {
/**
* Simple wrapper to pass the litho parameters through the prefix search.
*/
private static final class LithoFilterParameters {
final String identifier;
final String path;
final byte[] buffer;
LithoFilterParameters(String lithoIdentifier, String lithoPath, byte[] buffer) {
this.identifier = lithoIdentifier;
this.path = lithoPath;
this.buffer = buffer;
}
@NonNull
@Override
public String toString() {
// Estimate the percentage of the buffer that are Strings.
StringBuilder builder = new StringBuilder(Math.max(100, buffer.length / 2));
builder.append( "ID: ");
builder.append(identifier);
builder.append(" Path: ");
builder.append(path);
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
builder.append(" BufferStrings: ");
findAsciiStrings(builder, buffer);
}
return builder.toString();
}
/**
* Search through a byte array for all ASCII strings.
*/
static void findAsciiStrings(StringBuilder builder, byte[] buffer) {
// Valid ASCII values (ignore control characters).
final int minimumAscii = 32; // 32 = space character
final int maximumAscii = 126; // 127 = delete character
final int minimumAsciiStringLength = 4; // Minimum length of an ASCII string to include.
String delimitingCharacter = ""; // Non ascii character, to allow easier log filtering.
final int length = buffer.length;
int start = 0;
int end = 0;
while (end < length) {
int value = buffer[end];
if (value < minimumAscii || value > maximumAscii || end == length - 1) {
if (end - start >= minimumAsciiStringLength) {
for (int i = start; i < end; i++) {
builder.append((char) buffer[i]);
}
builder.append(delimitingCharacter);
}
start = end + 1;
}
end++;
}
}
}
/**
* Litho layout fixed thread pool size override.
* <p>
* Unpatched YouTube uses a layout fixed thread pool between 1 and 3 threads:
* <pre>
* 1 thread - > Device has less than 6 cores
* 2 threads -> Device has over 6 cores and less than 6GB of memory
* 3 threads -> Device has over 6 cores and more than 6GB of memory
* </pre>
*
* Using more than 1 thread causes layout issues such as the You tab watch/playlist shelf
* that is sometimes incorrectly hidden (ReVanced is not hiding it), and seems to
* fix a race issue if using the active navigation tab status with litho filtering.
*/
private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1;
/**
* Placeholder for actual filters.
*/
private static final class DummyFilter extends Filter { }
private static final Filter[] filters = new Filter[] {
new DummyFilter() // Replaced patching, do not touch.
};
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/**
* Because litho filtering is multi-threaded and the buffer is passed in from a different injection point,
* the buffer is saved to a ThreadLocal so each calling thread does not interfere with other threads.
*/
private static final ThreadLocal<byte[]> bufferThreadLocal = new ThreadLocal<>();
static {
for (Filter filter : filters) {
filterUsingCallbacks(identifierSearchTree, filter,
filter.identifierCallbacks, Filter.FilterContentType.IDENTIFIER);
filterUsingCallbacks(pathSearchTree, filter,
filter.pathCallbacks, Filter.FilterContentType.PATH);
}
Logger.printDebug(() -> "Using: "
+ identifierSearchTree.numberOfPatterns() + " identifier filters"
+ " (" + identifierSearchTree.getEstimatedMemorySize() + " KB), "
+ pathSearchTree.numberOfPatterns() + " path filters"
+ " (" + pathSearchTree.getEstimatedMemorySize() + " KB)");
}
private static void filterUsingCallbacks(StringTrieSearch pathSearchTree,
Filter filter, List<StringFilterGroup> groups,
Filter.FilterContentType type) {
String filterSimpleName = filter.getClass().getSimpleName();
for (StringFilterGroup group : groups) {
if (!group.includeInSearch()) {
continue;
}
for (String pattern : group.filters) {
pathSearchTree.addPattern(pattern, (textSearched, matchedStartIndex,
matchedLength, callbackParameter) -> {
if (!group.isEnabled()) return false;
LithoFilterParameters parameters = (LithoFilterParameters) callbackParameter;
final boolean isFiltered = filter.isFiltered(parameters.identifier,
parameters.path, parameters.buffer, group, type, matchedStartIndex);
if (isFiltered && BaseSettings.DEBUG.get()) {
if (type == Filter.FilterContentType.IDENTIFIER) {
Logger.printDebug(() -> "Filtered " + filterSimpleName
+ " identifier: " + parameters.identifier);
} else {
Logger.printDebug(() -> "Filtered " + filterSimpleName
+ " path: " + parameters.path);
}
}
return isFiltered;
}
);
}
}
}
/**
* Injection point. Called off the main thread.
* Targets 20.22+
*/
public static void setProtoBuffer(byte[] buffer) {
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
// The buffer will be cleared from memory after a new buffer is set by the same thread,
// or when the calling thread eventually dies.
bufferThreadLocal.set(buffer);
}
/**
* Injection point. Called off the main thread.
* Targets 20.21 and lower.
*/
public static void setProtoBuffer(@Nullable ByteBuffer buffer) {
// Set the buffer to a thread local. The buffer will remain in memory, even after the call to #filter completes.
// This is intentional, as it appears the buffer can be set once and then filtered multiple times.
// The buffer will be cleared from memory after a new buffer is set by the same thread,
// or when the calling thread eventually dies.
if (buffer == null || !buffer.hasArray()) {
// It appears the buffer can be cleared out just before the call to #filter()
// Ignore this null value and retain the last buffer that was set.
Logger.printDebug(() -> "Ignoring null or empty buffer: " + buffer);
} else {
setProtoBuffer(buffer.array());
}
}
/**
* Injection point.
*/
public static boolean isFiltered(String lithoIdentifier, StringBuilder pathBuilder) {
try {
if (lithoIdentifier.isEmpty() && pathBuilder.length() == 0) {
return false;
}
byte[] buffer = bufferThreadLocal.get();
// Potentially the buffer may have been null or never set up until now.
// Use an empty buffer so the litho id/path filters still work correctly.
if (buffer == null) {
buffer = EMPTY_BYTE_ARRAY;
}
LithoFilterParameters parameter = new LithoFilterParameters(
lithoIdentifier, pathBuilder.toString(), buffer);
Logger.printDebug(() -> "Searching " + parameter);
if (identifierSearchTree.matches(parameter.identifier, parameter)) {
return true;
}
if (pathSearchTree.matches(parameter.path, parameter)) {
return true;
}
} catch (Exception ex) {
Logger.printException(() -> "isFiltered failure", ex);
}
return false;
}
/**
* Injection point.
*/
public static int getExecutorCorePoolSize(int originalCorePoolSize) {
if (originalCorePoolSize != LITHO_LAYOUT_THREAD_POOL_SIZE) {
Logger.printDebug(() -> "Overriding core thread pool size from: " + originalCorePoolSize
+ " to: " + LITHO_LAYOUT_THREAD_POOL_SIZE);
}
return LITHO_LAYOUT_THREAD_POOL_SIZE;
}
/**
* Injection point.
*/
public static int getExecutorMaxThreads(int originalMaxThreads) {
if (originalMaxThreads != LITHO_LAYOUT_THREAD_POOL_SIZE) {
Logger.printDebug(() -> "Overriding max thread pool size from: " + originalMaxThreads
+ " to: " + LITHO_LAYOUT_THREAD_POOL_SIZE);
}
return LITHO_LAYOUT_THREAD_POOL_SIZE;
}
}

View File

@@ -17,9 +17,6 @@ public class LinkSanitizer {
public LinkSanitizer(String ... parametersToRemove) { public LinkSanitizer(String ... parametersToRemove) {
final int parameterCount = parametersToRemove.length; final int parameterCount = parametersToRemove.length;
if (parameterCount == 0) {
throw new IllegalArgumentException("No parameters specified");
}
// List is faster if only checking a few parameters. // List is faster if only checking a few parameters.
this.parametersToRemove = parameterCount > 4 this.parametersToRemove = parameterCount > 4
@@ -38,12 +35,23 @@ public class LinkSanitizer {
public Uri sanitizeUri(Uri uri) { public Uri sanitizeUri(Uri uri) {
try { try {
String scheme = uri.getScheme();
if (scheme == null || !(scheme.equals("http") || scheme.equals("https"))) {
// Opening YouTube share sheet 'other' option passes the video title as a URI.
// Checking !uri.isHierarchical() works for all cases, except if the
// video title starts with / and then it's hierarchical but still an invalid URI.
Logger.printDebug(() -> "Ignoring uri: " + uri);
return uri;
}
Uri.Builder builder = uri.buildUpon().clearQuery(); Uri.Builder builder = uri.buildUpon().clearQuery();
for (String paramName : uri.getQueryParameterNames()) { if (!parametersToRemove.isEmpty()) {
if (!parametersToRemove.contains(paramName)) { for (String paramName : uri.getQueryParameterNames()) {
for (String value : uri.getQueryParameters(paramName)) { if (!parametersToRemove.contains(paramName)) {
builder.appendQueryParameter(paramName, value); for (String value : uri.getQueryParameters(paramName)) {
builder.appendQueryParameter(paramName, value);
}
} }
} }
} }

View File

@@ -7,15 +7,16 @@ import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.preference.PreferenceFragment; import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toolbar; import android.widget.Toolbar;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment; import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragment;
import app.revanced.extension.shared.ui.Dim;
/** /**
* Base class for hooking activities to inject a custom PreferenceFragment with a toolbar. * Base class for hooking activities to inject a custom PreferenceFragment with a toolbar.
@@ -25,13 +26,13 @@ import app.revanced.extension.shared.settings.preference.ToolbarPreferenceFragme
public abstract class BaseActivityHook extends Activity { public abstract class BaseActivityHook extends Activity {
private static final int ID_REVANCED_SETTINGS_FRAGMENTS = private static final int ID_REVANCED_SETTINGS_FRAGMENTS =
getResourceIdentifierOrThrow("revanced_settings_fragments", "id"); getResourceIdentifierOrThrow(ResourceType.ID, "revanced_settings_fragments");
private static final int ID_REVANCED_TOOLBAR_PARENT = private static final int ID_REVANCED_TOOLBAR_PARENT =
getResourceIdentifierOrThrow("revanced_toolbar_parent", "id"); getResourceIdentifierOrThrow(ResourceType.ID, "revanced_toolbar_parent");
public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR = public static final int LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR =
getResourceIdentifierOrThrow("revanced_settings_with_toolbar", "layout"); getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_settings_with_toolbar");
private static final int STRING_REVANCED_SETTINGS_TITLE = private static final int STRING_REVANCED_SETTINGS_TITLE =
getResourceIdentifierOrThrow("revanced_settings_title", "string"); getResourceIdentifierOrThrow(ResourceType.STRING, "revanced_settings_title");
/** /**
* Layout parameters for the toolbar, extracted from the dummy toolbar. * Layout parameters for the toolbar, extracted from the dummy toolbar.
@@ -109,13 +110,12 @@ public abstract class BaseActivityHook extends Activity {
toolbar.setNavigationOnClickListener(getNavigationClickListener(activity)); toolbar.setNavigationOnClickListener(getNavigationClickListener(activity));
toolbar.setTitle(STRING_REVANCED_SETTINGS_TITLE); toolbar.setTitle(STRING_REVANCED_SETTINGS_TITLE);
final int margin = Utils.dipToPixels(16); toolbar.setTitleMarginStart(Dim.dp16);
toolbar.setTitleMarginStart(margin); toolbar.setTitleMarginEnd(Dim.dp16);
toolbar.setTitleMarginEnd(margin);
TextView toolbarTextView = Utils.getChildView(toolbar, false, view -> view instanceof TextView); TextView toolbarTextView = Utils.getChildView(toolbar, false, view -> view instanceof TextView);
if (toolbarTextView != null) { if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor()); toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); toolbarTextView.setTextSize(20);
} }
setToolbarLayoutParams(toolbar); setToolbarLayoutParams(toolbar);
@@ -124,16 +124,18 @@ public abstract class BaseActivityHook extends Activity {
toolBarParent.addView(toolbar, 0); toolBarParent.addView(toolbar, 0);
} }
/**
* Returns the resource ID for the content view layout.
*/
protected int getContentViewResourceId() {
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
}
/** /**
* Customizes the activity's theme. * Customizes the activity's theme.
*/ */
protected abstract void customizeActivityTheme(Activity activity); protected abstract void customizeActivityTheme(Activity activity);
/**
* Returns the resource ID for the content view layout.
*/
protected abstract int getContentViewResourceId();
/** /**
* Returns the background color for the toolbar. * Returns the background color for the toolbar.
*/ */

View File

@@ -4,7 +4,8 @@ import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme; import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme;
import static app.revanced.extension.shared.settings.Setting.parent; import static app.revanced.extension.shared.settings.Setting.parent;
import static app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch.AudioStreamLanguageOverrideAvailability;
import app.revanced.extension.shared.Logger;
/** /**
* Settings shared across multiple apps. * Settings shared across multiple apps.
@@ -25,16 +26,24 @@ public class BaseSettings {
* Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing. * Use the icons declared in the preferences created during patching. If no icons or styles are declared then this setting does nothing.
*/ */
public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true); public static final BooleanSetting SHOW_MENU_ICONS = new BooleanSetting("revanced_show_menu_icons", TRUE, true);
/**
* Do not use this setting directly. Instead use {@link app.revanced.extension.shared.Utils#appIsUsingBoldIcons()}
*/
public static final BooleanSetting SETTINGS_DISABLE_BOLD_ICONS = new BooleanSetting("revanced_settings_disable_bold_icons", FALSE, true);
public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true); public static final BooleanSetting SETTINGS_SEARCH_HISTORY = new BooleanSetting("revanced_settings_search_history", TRUE, true);
public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", ""); public static final StringSetting SETTINGS_SEARCH_ENTRIES = new StringSetting("revanced_settings_search_entries", "");
/**
* The first time the app was launched with no previous app data (either a clean install, or after wiping app data).
*/
public static final LongSetting FIRST_TIME_APP_LAUNCHED = new LongSetting("revanced_last_time_app_was_launched", -1L, false, false);
// //
// Settings shared by YouTube and YouTube Music. // Settings shared by YouTube and YouTube Music.
// //
public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message"); public static final BooleanSetting SPOOF_VIDEO_STREAMS = new BooleanSetting("revanced_spoof_video_streams", TRUE, true, "revanced_spoof_video_streams_user_dialog_message");
public static final EnumSetting<AppLanguage> SPOOF_VIDEO_STREAMS_LANGUAGE = new EnumSetting<>("revanced_spoof_video_streams_language", AppLanguage.DEFAULT, new AudioStreamLanguageOverrideAvailability());
public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS)); public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting SANITIZE_SHARED_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE); public static final BooleanSetting SANITIZE_SHARED_LINKS = new BooleanSetting("revanced_sanitize_sharing_links", TRUE);
@@ -44,4 +53,15 @@ public class BaseSettings {
public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", BrandingTheme.ORIGINAL, true); public static final EnumSetting<BrandingTheme> CUSTOM_BRANDING_ICON = new EnumSetting<>("revanced_custom_branding_icon", BrandingTheme.ORIGINAL, true);
public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true); public static final IntegerSetting CUSTOM_BRANDING_NAME = new IntegerSetting("revanced_custom_branding_name", 1, true);
public static final StringSetting DISABLED_FEATURE_FLAGS = new StringSetting("revanced_disabled_feature_flags", "", true, parent(DEBUG));
static {
final long now = System.currentTimeMillis();
if (FIRST_TIME_APP_LAUNCHED.get() < 0) {
Logger.printInfo(() -> "First launch of installation with no prior app data");
FIRST_TIME_APP_LAUNCHED.save(now);
}
}
} }

View File

@@ -58,6 +58,23 @@ public abstract class Setting<T> {
}; };
} }
/**
* Availability based on a single parent setting being disabled.
*/
public static Availability parentNot(BooleanSetting parent) {
return new Availability() {
@Override
public boolean isAvailable() {
return !parent.get();
}
@Override
public List<Setting<?>> getParentSettings() {
return Collections.singletonList(parent);
}
};
}
/** /**
* Availability based on all parents being enabled. * Availability based on all parents being enabled.
*/ */
@@ -392,10 +409,13 @@ public abstract class Setting<T> {
/** /**
* Get the parent Settings that this setting depends on. * Get the parent Settings that this setting depends on.
* @return List of parent Settings (e.g., BooleanSetting or EnumSetting), or empty list if no dependencies exist. * @return List of parent Settings, or empty list if no dependencies exist.
* Defensive: handles null availability or missing getParentSettings() override.
*/ */
public List<Setting<?>> getParentSettings() { public List<Setting<?>> getParentSettings() {
return availability == null ? Collections.emptyList() : availability.getParentSettings(); return availability == null
? Collections.emptyList()
: Objects.requireNonNullElse(availability.getParentSettings(), Collections.emptyList());
} }
/** /**

View File

@@ -0,0 +1,14 @@
package app.revanced.extension.shared.settings;
import static app.revanced.extension.shared.settings.Setting.parent;
import static java.lang.Boolean.FALSE;
public class YouTubeAndMusicSettings extends BaseSettings {
// Custom filter
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
// Miscellaneous
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
}

View File

@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.BooleanSetting;
@@ -103,10 +104,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
* so all app specific {@link Setting} instances are loaded before this method returns. * so all app specific {@link Setting} instances are loaded before this method returns.
*/ */
protected void initialize() { protected void initialize() {
String preferenceResourceName = BaseSettings.SHOW_MENU_ICONS.get() String preferenceResourceName;
? "revanced_prefs_icons" if (BaseSettings.SHOW_MENU_ICONS.get()) {
: "revanced_prefs"; preferenceResourceName = Utils.appIsUsingBoldIcons()
final var identifier = Utils.getResourceIdentifier(preferenceResourceName, "xml"); ? "revanced_prefs_icons_bold"
: "revanced_prefs_icons";
} else {
preferenceResourceName = "revanced_prefs";
}
final var identifier = Utils.getResourceIdentifier(ResourceType.XML, preferenceResourceName);
if (identifier == 0) return; if (identifier == 0) return;
addPreferencesFromResource(identifier); addPreferencesFromResource(identifier);

View File

@@ -1,7 +1,6 @@
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.Utils.getResourceIdentifierOrThrow; import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
import android.app.Dialog; import android.app.Dialog;
@@ -32,11 +31,13 @@ import java.util.Locale;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
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.ui.ColorDot; import app.revanced.extension.shared.ui.ColorDot;
import app.revanced.extension.shared.ui.CustomDialog; import app.revanced.extension.shared.ui.CustomDialog;
import app.revanced.extension.shared.ui.Dim;
/** /**
* A custom preference for selecting a color via a hexadecimal code or a color picker dialog. * A custom preference for selecting a color via a hexadecimal code or a color picker dialog.
@@ -81,13 +82,13 @@ public class ColorPickerPreference extends EditTextPreference {
private boolean opacitySliderEnabled = false; private boolean opacitySliderEnabled = false;
public static final int ID_REVANCED_COLOR_PICKER_VIEW = public static final int ID_REVANCED_COLOR_PICKER_VIEW =
getResourceIdentifierOrThrow("revanced_color_picker_view", "id"); getResourceIdentifierOrThrow(ResourceType.ID, "revanced_color_picker_view");
public static final int ID_PREFERENCE_COLOR_DOT = public static final int ID_PREFERENCE_COLOR_DOT =
getResourceIdentifierOrThrow("preference_color_dot", "id"); getResourceIdentifierOrThrow(ResourceType.ID, "preference_color_dot");
public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET = public static final int LAYOUT_REVANCED_COLOR_DOT_WIDGET =
getResourceIdentifierOrThrow("revanced_color_dot_widget", "layout"); getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_dot_widget");
public static final int LAYOUT_REVANCED_COLOR_PICKER = public static final int LAYOUT_REVANCED_COLOR_PICKER =
getResourceIdentifierOrThrow("revanced_color_picker", "layout"); getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_picker");
/** /**
* Removes non valid hex characters, converts to all uppercase, * Removes non valid hex characters, converts to all uppercase,
@@ -310,11 +311,8 @@ public class ColorPickerPreference extends EditTextPreference {
inputLayout.setGravity(Gravity.CENTER_VERTICAL); inputLayout.setGravity(Gravity.CENTER_VERTICAL);
dialogColorDot = new View(context); dialogColorDot = new View(context);
LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams previewParams = new LinearLayout.LayoutParams(Dim.dp20,Dim.dp20);
dipToPixels(20), previewParams.setMargins(Dim.dp16, 0, Dim.dp10, 0);
dipToPixels(20)
);
previewParams.setMargins(dipToPixels(16), 0, dipToPixels(10), 0);
dialogColorDot.setLayoutParams(previewParams); dialogColorDot.setLayoutParams(previewParams);
inputLayout.addView(dialogColorDot); inputLayout.addView(dialogColorDot);
updateDialogColorDot(); updateDialogColorDot();

View File

@@ -1,6 +1,5 @@
package app.revanced.extension.shared.settings.preference; package app.revanced.extension.shared.settings.preference;
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 android.annotation.SuppressLint; import android.annotation.SuppressLint;
@@ -21,6 +20,7 @@ import androidx.annotation.ColorInt;
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.ui.Dim;
/** /**
* A custom color picker view that allows the user to select a color using a hue slider, a saturation-value selector * A custom color picker view that allows the user to select a color using a hue slider, a saturation-value selector
@@ -54,28 +54,28 @@ public class ColorPickerView extends View {
} }
/** Expanded touch area for the hue and opacity bars to increase the touch-sensitive area. */ /** Expanded touch area for the hue and opacity bars to increase the touch-sensitive area. */
public static final float TOUCH_EXPANSION = dipToPixels(20f); public static final float TOUCH_EXPANSION = Dim.dp20;
/** Margin between different areas of the view (saturation-value selector, hue bar, and opacity slider). */ /** Margin between different areas of the view (saturation-value selector, hue bar, and opacity slider). */
private static final float MARGIN_BETWEEN_AREAS = dipToPixels(24); private static final float MARGIN_BETWEEN_AREAS = Dim.dp24;
/** Padding around the view. */ /** Padding around the view. */
private static final float VIEW_PADDING = dipToPixels(16); private static final float VIEW_PADDING = Dim.dp16;
/** Height of the hue bar. */ /** Height of the hue bar. */
private static final float HUE_BAR_HEIGHT = dipToPixels(12); private static final float HUE_BAR_HEIGHT = Dim.dp12;
/** Height of the opacity slider. */ /** Height of the opacity slider. */
private static final float OPACITY_BAR_HEIGHT = dipToPixels(12); private static final float OPACITY_BAR_HEIGHT = Dim.dp12;
/** Corner radius for the hue bar. */ /** Corner radius for the hue bar. */
private static final float HUE_CORNER_RADIUS = dipToPixels(6); private static final float HUE_CORNER_RADIUS = Dim.dp6;
/** Corner radius for the opacity slider. */ /** Corner radius for the opacity slider. */
private static final float OPACITY_CORNER_RADIUS = dipToPixels(6); private static final float OPACITY_CORNER_RADIUS = Dim.dp6;
/** Radius of the selector handles. */ /** Radius of the selector handles. */
private static final float SELECTOR_RADIUS = dipToPixels(12); private static final float SELECTOR_RADIUS = Dim.dp12;
/** Stroke width for the selector handle outlines. */ /** Stroke width for the selector handle outlines. */
private static final float SELECTOR_STROKE_WIDTH = 8; private static final float SELECTOR_STROKE_WIDTH = 8;
@@ -202,7 +202,7 @@ public class ColorPickerView extends View {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
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 = dipToPixels(250); final int minWidth = Dim.dp(250);
final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS) final int minHeight = (int) (minWidth * DESIRED_ASPECT_RATIO) + (int) (HUE_BAR_HEIGHT + MARGIN_BETWEEN_AREAS)
+ (opacitySliderEnabled ? (int) (OPACITY_BAR_HEIGHT + MARGIN_BETWEEN_AREAS) : 0); + (opacitySliderEnabled ? (int) (OPACITY_BAR_HEIGHT + MARGIN_BETWEEN_AREAS) : 0);

View File

@@ -20,6 +20,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ui.CustomDialog; import app.revanced.extension.shared.ui.CustomDialog;
@@ -30,14 +31,18 @@ import app.revanced.extension.shared.ui.CustomDialog;
@SuppressWarnings({"unused", "deprecation"}) @SuppressWarnings({"unused", "deprecation"})
public class CustomDialogListPreference extends ListPreference { public class CustomDialogListPreference extends ListPreference {
public static final int ID_REVANCED_CHECK_ICON = public static final int ID_REVANCED_CHECK_ICON = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_check_icon", "id"); ResourceType.ID, "revanced_check_icon");
public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER = public static final int ID_REVANCED_CHECK_ICON_PLACEHOLDER = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_check_icon_placeholder", "id"); ResourceType.ID, "revanced_check_icon_placeholder");
public static final int ID_REVANCED_ITEM_TEXT = public static final int ID_REVANCED_ITEM_TEXT = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_item_text", "id"); ResourceType.ID, "revanced_item_text");
public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED = public static final int LAYOUT_REVANCED_CUSTOM_LIST_ITEM_CHECKED = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_custom_list_item_checked", "layout"); ResourceType.LAYOUT, "revanced_custom_list_item_checked");
public static final int DRAWABLE_CHECKMARK = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_custom_checkmark");
public static final int DRAWABLE_CHECKMARK_BOLD = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_custom_checkmark_bold");
private String staticSummary = null; private String staticSummary = null;
private CharSequence[] highlightedEntriesForDialog = null; private CharSequence[] highlightedEntriesForDialog = null;
@@ -125,9 +130,13 @@ public class CustomDialogListPreference extends ListPreference {
LayoutInflater inflater = LayoutInflater.from(getContext()); LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(layoutResourceId, parent, false); view = inflater.inflate(layoutResourceId, parent, false);
holder = new SubViewDataContainer(); holder = new SubViewDataContainer();
holder.checkIcon = view.findViewById(ID_REVANCED_CHECK_ICON);
holder.placeholder = view.findViewById(ID_REVANCED_CHECK_ICON_PLACEHOLDER); holder.placeholder = view.findViewById(ID_REVANCED_CHECK_ICON_PLACEHOLDER);
holder.itemText = view.findViewById(ID_REVANCED_ITEM_TEXT); holder.itemText = view.findViewById(ID_REVANCED_ITEM_TEXT);
holder.checkIcon = view.findViewById(ID_REVANCED_CHECK_ICON);
holder.checkIcon.setImageResource(Utils.appIsUsingBoldIcons()
? DRAWABLE_CHECKMARK_BOLD
: DRAWABLE_CHECKMARK
);
view.setTag(holder); view.setTag(holder);
} else { } else {
holder = (SubViewDataContainer) view.getTag(); holder = (SubViewDataContainer) view.getTag();

View File

@@ -0,0 +1,625 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.preference.Preference;
import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Pair;
import android.util.SparseBooleanArray;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Space;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.patches.EnableDebuggingPatch;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.ui.CustomDialog;
import app.revanced.extension.shared.ui.Dim;
/**
* A custom preference that opens a dialog for managing feature flags.
* Allows moving boolean flags between active and blocked states with advanced selection.
*/
@SuppressWarnings({"deprecation", "unused"})
public class FeatureFlagsManagerPreference extends Preference {
private static final int DRAWABLE_REVANCED_SETTINGS_SELECT_ALL =
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_select_all");
private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL =
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_deselect_all");
private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL =
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_copy_all");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE =
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_one");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE =
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_double");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE =
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_one");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE =
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_double");
/**
* Flags to hide from the UI.
*/
private static final Set<Long> FLAGS_TO_IGNORE = Set.of(
45386834L, // 'You' tab settings icon.
45685201L // Bold icons. Forcing off interferes with patch changes and YT icons are broken.
);
/**
* Tracks state for range selection in ListView.
*/
private static class ListViewSelectionState {
int lastClickedPosition = -1; // Position of the last clicked item.
boolean isRangeSelecting = false; // True while a range is being selected.
}
/**
* Helper class to pass ListView and Adapter together.
*/
private record ColumnViews(ListView listView, FlagAdapter adapter) {}
{
setOnPreferenceClickListener(pref -> {
showFlagsManagerDialog();
return true;
});
}
public FeatureFlagsManagerPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public FeatureFlagsManagerPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public FeatureFlagsManagerPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FeatureFlagsManagerPreference(Context context) {
super(context);
}
/**
* Shows the main dialog for managing feature flags.
*/
private void showFlagsManagerDialog() {
if (!BaseSettings.DEBUG.get()) {
Utils.showToastShort(str("revanced_debug_logs_disabled"));
return;
}
Context context = getContext();
// Load all known and disabled flags.
TreeSet<Long> allKnownFlags = new TreeSet<>(EnableDebuggingPatch.getAllLoggedFlags());
allKnownFlags.removeAll(FLAGS_TO_IGNORE);
TreeSet<Long> disabledFlags = new TreeSet<>(EnableDebuggingPatch.parseFlags(
BaseSettings.DISABLED_FEATURE_FLAGS.get()));
disabledFlags.removeAll(FLAGS_TO_IGNORE);
if (allKnownFlags.isEmpty() && disabledFlags.isEmpty()) {
// String does not need to be localized because it's basically impossible
// to reach the settings menu without encountering at least 1 flag.
Utils.showToastShort("No feature flags logged yet");
return;
}
TreeSet<Long> availableFlags = new TreeSet<>(allKnownFlags);
availableFlags.removeAll(disabledFlags);
TreeSet<Long> blockedFlags = new TreeSet<>(disabledFlags);
Pair<Dialog, LinearLayout> dialogPair = CustomDialog.create(
context,
getTitle() != null ? getTitle().toString() : "",
null,
null,
str("revanced_settings_save"),
() -> saveFlags(blockedFlags),
() -> {},
str("revanced_settings_reset"),
this::resetFlags,
true
);
LinearLayout mainLayout = dialogPair.second;
LinearLayout.LayoutParams contentParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, 0, 1.0f);
// Insert content before the dialog button row.
View contentView = createContentView(context, availableFlags, blockedFlags);
mainLayout.addView(contentView, mainLayout.getChildCount() - 1, contentParams);
Dialog dialog = dialogPair.first;
dialog.show();
Window window = dialog.getWindow();
if (window != null) {
Utils.setDialogWindowParameters(window, Gravity.CENTER, 0, 100, false);
}
}
/**
* Creates the main content view with two columns.
*/
private View createContentView(Context context, TreeSet<Long> availableFlags, TreeSet<Long> blockedFlags) {
LinearLayout contentLayout = new LinearLayout(context);
contentLayout.setOrientation(LinearLayout.VERTICAL);
// Headers.
TextView availableHeader = createHeader(context, "revanced_debug_feature_flags_manager_active_header");
TextView blockedHeader = createHeader(context, "revanced_debug_feature_flags_manager_blocked_header");
LinearLayout headersLayout = new LinearLayout(context);
headersLayout.setOrientation(LinearLayout.HORIZONTAL);
headersLayout.addView(availableHeader, new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
headersLayout.addView(blockedHeader, new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
// Columns.
View leftColumn = createColumn(context, availableFlags, availableHeader);
View rightColumn = createColumn(context, blockedFlags, blockedHeader);
ColumnViews leftViews = (ColumnViews) leftColumn.getTag();
ColumnViews rightViews = (ColumnViews) rightColumn.getTag();
updateHeaderCount(availableHeader, leftViews.adapter);
updateHeaderCount(blockedHeader, rightViews.adapter);
// Main columns layout.
LinearLayout columnsLayout = new LinearLayout(context);
columnsLayout.setOrientation(LinearLayout.HORIZONTAL);
columnsLayout.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
columnsLayout.addView(leftColumn, new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
Space spaceBetweenColumns = new Space(context);
spaceBetweenColumns.setLayoutParams(new LinearLayout.LayoutParams(Dim.dp8, ViewGroup.LayoutParams.MATCH_PARENT));
columnsLayout.addView(spaceBetweenColumns);
columnsLayout.addView(rightColumn, new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.MATCH_PARENT, 1f));
// Move buttons below columns.
Pair<LinearLayout, LinearLayout> moveButtons = createMoveButtons(context,
leftViews.listView, rightViews.listView,
availableFlags, blockedFlags, availableHeader, blockedHeader);
// Layout for buttons row.
LinearLayout buttonsRow = new LinearLayout(context);
buttonsRow.setOrientation(LinearLayout.HORIZONTAL);
buttonsRow.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
buttonsRow.addView(moveButtons.first, new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
Space spaceBetweenButtons = new Space(context);
spaceBetweenButtons.setLayoutParams(new LinearLayout.LayoutParams(Dim.dp8, ViewGroup.LayoutParams.WRAP_CONTENT));
buttonsRow.addView(spaceBetweenButtons);
buttonsRow.addView(moveButtons.second, new LinearLayout.LayoutParams(
0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
contentLayout.addView(headersLayout);
contentLayout.addView(columnsLayout);
contentLayout.addView(buttonsRow);
return contentLayout;
}
/**
* Creates a header TextView.
*/
private TextView createHeader(Context context, String tag) {
TextView textview = new TextView(context);
textview.setTag(tag);
textview.setTextSize(16);
textview.setTextColor(Utils.getAppForegroundColor());
textview.setGravity(Gravity.CENTER);
return textview;
}
/**
* Creates a single column (search + buttons + list).
*/
private View createColumn(Context context, TreeSet<Long> flags, TextView countText) {
LinearLayout wrapper = new LinearLayout(context);
wrapper.setOrientation(LinearLayout.VERTICAL);
Pair<ListView, FlagAdapter> pair = createListView(context, flags, countText);
ListView listView = pair.first;
FlagAdapter adapter = pair.second;
EditText search = createSearchBox(context, adapter, listView, countText);
LinearLayout buttons = createActionButtons(context, listView, adapter);
listView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f));
ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
Dim.roundedCorners(10), null, null));
background.getPaint().setColor(Utils.getEditTextBackground());
listView.setPadding(0, Dim.dp4, 0, Dim.dp4);
listView.setBackground(background);
listView.setOverScrollMode(View.OVER_SCROLL_NEVER);
wrapper.addView(search);
wrapper.addView(buttons);
wrapper.addView(listView);
// Save references for move buttons.
wrapper.setTag(new ColumnViews(listView, adapter));
return wrapper;
}
/**
* Updates the header text with the current count.
*/
private void updateHeaderCount(TextView header, FlagAdapter adapter) {
header.setText(str((String) header.getTag(), adapter.getCount()));
}
/**
* Creates a search box that filters the list.
*/
@SuppressLint("ClickableViewAccessibility")
private EditText createSearchBox(Context context, FlagAdapter adapter, ListView listView, TextView countText) {
EditText search = new EditText(context);
search.setInputType(InputType.TYPE_CLASS_NUMBER);
search.setTextSize(16);
search.setHint(str("revanced_debug_feature_flags_manager_search_hint"));
search.setHapticFeedbackEnabled(false);
search.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
search.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
adapter.setSearchQuery(s.toString());
listView.clearChoices();
updateHeaderCount(countText, adapter);
Drawable clearIcon = context.getResources().getDrawable(android.R.drawable.ic_menu_close_clear_cancel);
clearIcon.setBounds(0, 0, Dim.dp20, Dim.dp20);
search.setCompoundDrawables(null, null, TextUtils.isEmpty(s) ? null : clearIcon, null);
}
@Override public void afterTextChanged(Editable s) {}
});
search.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP) {
Drawable[] compoundDrawables = search.getCompoundDrawables();
if (compoundDrawables[2] != null &&
event.getRawX() >= (search.getRight() - compoundDrawables[2].getBounds().width())) {
search.setText("");
return true;
}
}
return false;
});
return search;
}
/**
* Creates action buttons.
*/
private LinearLayout createActionButtons(Context context, ListView listView, FlagAdapter adapter) {
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(Gravity.CENTER);
row.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
ImageButton selectAll = createButton(context, DRAWABLE_REVANCED_SETTINGS_SELECT_ALL,
() -> {
for (int i = 0, count = adapter.getCount(); i < count; i++) {
listView.setItemChecked(i, true);
}
});
ImageButton clearAll = createButton(context, DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL,
() -> {
listView.clearChoices();
adapter.notifyDataSetChanged();
});
ImageButton copy = createButton(context, DRAWABLE_REVANCED_SETTINGS_COPY_ALL,
() -> {
List<String> items = new ArrayList<>();
SparseBooleanArray checked = listView.getCheckedItemPositions();
if (checked.size() > 0) {
for (int i = 0, count = adapter.getCount(); i < count; i++) {
if (checked.get(i)) {
items.add(adapter.getItem(i));
}
}
} else {
for (Long flag : adapter.getFullFlags()) {
items.add(String.valueOf(flag));
}
}
Utils.setClipboard(TextUtils.join("\n", items));
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_copied"));
});
row.addView(selectAll);
row.addView(clearAll);
row.addView(copy);
return row;
}
/**
* Creates the move buttons (left and right groups).
*/
private Pair<LinearLayout, LinearLayout> createMoveButtons(Context context,
ListView availableListView, ListView blockedListView,
TreeSet<Long> availableFlags, TreeSet<Long> blockedFlags,
TextView availableCountText, TextView blockedCountText) {
// Left group: >> >
LinearLayout leftButtons = new LinearLayout(context);
leftButtons.setOrientation(LinearLayout.HORIZONTAL);
leftButtons.setGravity(Gravity.CENTER);
ImageButton moveAllRight = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE,
() -> moveFlags(availableListView, blockedListView, availableFlags, blockedFlags,
availableCountText, blockedCountText, true));
ImageButton moveOneRight = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE,
() -> moveFlags(availableListView, blockedListView, availableFlags, blockedFlags,
availableCountText, blockedCountText, false));
leftButtons.addView(moveAllRight);
leftButtons.addView(moveOneRight);
// Right group: < <<
LinearLayout rightButtons = new LinearLayout(context);
rightButtons.setOrientation(LinearLayout.HORIZONTAL);
rightButtons.setGravity(Gravity.CENTER);
ImageButton moveOneLeft = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE,
() -> moveFlags(blockedListView, availableListView, blockedFlags, availableFlags,
blockedCountText, availableCountText, false));
ImageButton moveAllLeft = createButton(context, DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE,
() -> moveFlags(blockedListView, availableListView, blockedFlags, availableFlags,
blockedCountText, availableCountText, true));
rightButtons.addView(moveOneLeft);
rightButtons.addView(moveAllLeft);
return new Pair<>(leftButtons, rightButtons);
}
/**
* Creates a styled ImageButton.
*/
@SuppressLint("ResourceType")
private ImageButton createButton(Context context, int drawableResId, Runnable action) {
ImageButton button = new ImageButton(context);
button.setImageResource(drawableResId);
button.setScaleType(ImageView.ScaleType.CENTER);
int[] attrs = {android.R.attr.selectableItemBackgroundBorderless};
//noinspection Recycle
TypedArray ripple = context.obtainStyledAttributes(attrs);
button.setBackgroundDrawable(ripple.getDrawable(0));
ripple.close();
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(Dim.dp32, Dim.dp32);
params.setMargins(Dim.dp8, Dim.dp8, Dim.dp8, Dim.dp8);
button.setLayoutParams(params);
button.setOnClickListener(v -> action.run());
return button;
}
/**
* Custom adapter with search filtering.
*/
private static class FlagAdapter extends ArrayAdapter<String> {
private final TreeSet<Long> fullFlags;
private String searchQuery = "";
public FlagAdapter(Context context, TreeSet<Long> fullFlags) {
super(context, android.R.layout.simple_list_item_multiple_choice, new ArrayList<>());
this.fullFlags = fullFlags;
updateFiltered();
}
public void setSearchQuery(String query) {
searchQuery = query == null ? "" : query.trim();
updateFiltered();
}
private void updateFiltered() {
clear();
for (Long flag : fullFlags) {
String flagString = String.valueOf(flag);
if (searchQuery.isEmpty() || flagString.contains(searchQuery)) {
add(flagString);
}
}
notifyDataSetChanged();
}
public void refresh() {
updateFiltered();
}
public List<Long> getFullFlags() {
return new ArrayList<>(fullFlags);
}
}
/**
* Creates a ListView with filtering, multi-select, and range selection.
*/
@SuppressLint("ClickableViewAccessibility")
private Pair<ListView, FlagAdapter> createListView(Context context,
TreeSet<Long> flags, TextView countText) {
ListView listView = new ListView(context);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
listView.setDividerHeight(0);
FlagAdapter adapter = new FlagAdapter(context, flags);
listView.setAdapter(adapter);
final ListViewSelectionState state = new ListViewSelectionState();
listView.setOnItemClickListener((parent, view, position, id) -> {
if (!state.isRangeSelecting) {
state.lastClickedPosition = position;
} else {
state.isRangeSelecting = false;
}
});
listView.setOnItemLongClickListener((parent, view, position, id) -> {
if (state.lastClickedPosition == -1) {
listView.setItemChecked(position, true);
state.lastClickedPosition = position;
} else {
int start = Math.min(state.lastClickedPosition, position);
int end = Math.max(state.lastClickedPosition, position);
for (int i = start; i <= end; i++) {
listView.setItemChecked(i, true);
}
state.isRangeSelecting = true;
}
return true;
});
listView.setOnTouchListener((view, event) -> {
if (event.getAction() == MotionEvent.ACTION_UP && state.isRangeSelecting) {
state.isRangeSelecting = false;
}
return false;
});
return new Pair<>(listView, adapter);
}
/**
* Moves selected or all flags from one list to another.
*
* @param fromListView Source ListView.
* @param toListView Destination ListView.
* @param fromFlags Source flag set.
* @param toFlags Destination flag set.
* @param fromCountText Header showing count of source items.
* @param toCountText Header showing count of destination items.
* @param moveAll If true, move all items; if false, move only selected.
*/
private void moveFlags(ListView fromListView, ListView toListView,
TreeSet<Long> fromFlags, TreeSet<Long> toFlags,
TextView fromCountText, TextView toCountText,
boolean moveAll) {
if (fromListView == null || toListView == null) return;
List<Long> flagsToMove = new ArrayList<>();
FlagAdapter fromAdapter = (FlagAdapter) fromListView.getAdapter();
if (moveAll) {
flagsToMove.addAll(fromFlags);
} else {
SparseBooleanArray checked = fromListView.getCheckedItemPositions();
for (int i = 0, count = fromAdapter.getCount(); i < count; i++) {
if (checked.get(i)) {
String item = fromAdapter.getItem(i);
if (item != null) {
flagsToMove.add(Long.parseLong(item));
}
}
}
}
if (flagsToMove.isEmpty()) return;
for (Long flag : flagsToMove) {
fromFlags.remove(flag);
toFlags.add(flag);
}
// Clear selections before refreshing.
fromListView.clearChoices();
toListView.clearChoices();
// Refresh both adapters.
fromAdapter.refresh();
((FlagAdapter) toListView.getAdapter()).refresh();
// Update headers.
updateHeaderCount(fromCountText, fromAdapter);
updateHeaderCount(toCountText, (FlagAdapter) toListView.getAdapter());
}
/**
* Saves blocked flags to settings.
*/
private void saveFlags(TreeSet<Long> blockedFlags) {
StringBuilder flagsString = new StringBuilder();
for (Long flag : blockedFlags) {
if (flagsString.length() > 0) {
flagsString.append("\n");
}
flagsString.append(flag);
}
BaseSettings.DISABLED_FEATURE_FLAGS.save(flagsString.toString());
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_saved"));
Logger.printDebug(() -> "Feature flags saved. Blocked: " + blockedFlags.size());
AbstractPreferenceFragment.showRestartDialog(getContext());
}
/**
* Resets all blocked flags.
*/
private void resetFlags() {
BaseSettings.DISABLED_FEATURE_FLAGS.save("");
Utils.showToastShort(str("revanced_debug_feature_flags_manager_toast_reset"));
AbstractPreferenceFragment.showRestartDialog(getContext());
}
}

View File

@@ -11,7 +11,6 @@ import android.preference.Preference;
import android.text.InputType; import android.text.InputType;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Pair; import android.util.Pair;
import android.util.TypedValue;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.EditText; import android.widget.EditText;
import android.widget.LinearLayout; import android.widget.LinearLayout;
@@ -35,7 +34,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
editText.setAutofillHints((String) null); editText.setAutofillHints((String) null);
} }
editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 7); // Use a smaller font to reduce text wrap. editText.setTextSize(14);
setOnPreferenceClickListener(this); setOnPreferenceClickListener(this);
} }

View File

@@ -1,7 +1,6 @@
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;
@@ -41,6 +40,7 @@ import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.requests.Requester; import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route; import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.ui.Dim;
/** /**
* Opens a dialog showing official links. * Opens a dialog showing official links.
@@ -222,11 +222,10 @@ class WebViewDialog extends Dialog {
LinearLayout mainLayout = new LinearLayout(getContext()); LinearLayout mainLayout = new LinearLayout(getContext());
mainLayout.setOrientation(LinearLayout.VERTICAL); mainLayout.setOrientation(LinearLayout.VERTICAL);
final int padding = dipToPixels(10); mainLayout.setPadding(Dim.dp10, Dim.dp10, Dim.dp10, Dim.dp10);
mainLayout.setPadding(padding, padding, padding, padding);
// Set rounded rectangle background. // Set rounded rectangle background.
ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape( ShapeDrawable mainBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(28), null, null)); Dim.roundedCorners(28), null, null));
mainBackground.getPaint().setColor(Utils.getDialogBackgroundColor()); mainBackground.getPaint().setColor(Utils.getDialogBackgroundColor());
mainLayout.setBackground(mainBackground); mainLayout.setBackground(mainBackground);

View File

@@ -8,7 +8,6 @@ import android.os.Build;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceGroup; import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.util.TypedValue;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.view.WindowInsets; import android.view.WindowInsets;
@@ -18,8 +17,11 @@ import android.widget.Toolbar;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook; import app.revanced.extension.shared.settings.BaseActivityHook;
import app.revanced.extension.shared.ui.Dim;
import app.revanced.extension.shared.settings.BaseSettings;
@SuppressWarnings({"deprecation", "NewApi"}) @SuppressWarnings({"deprecation", "NewApi"})
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment { public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
@@ -88,14 +90,13 @@ public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
toolbar.setNavigationIcon(getBackButtonDrawable()); toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss()); toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
final int margin = Utils.dipToPixels(16); toolbar.setTitleMargin(Dim.dp16, 0, Dim.dp16, 0);
toolbar.setTitleMargin(margin, 0, margin, 0);
TextView toolbarTextView = Utils.getChildView(toolbar, TextView toolbarTextView = Utils.getChildView(toolbar,
true, TextView.class::isInstance); true, TextView.class::isInstance);
if (toolbarTextView != null) { if (toolbarTextView != null) {
toolbarTextView.setTextColor(Utils.getAppForegroundColor()); toolbarTextView.setTextColor(Utils.getAppForegroundColor());
toolbarTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20); toolbarTextView.setTextSize(20);
} }
// Allow package-specific toolbar customization. // Allow package-specific toolbar customization.
@@ -134,8 +135,10 @@ public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
*/ */
@SuppressLint("UseCompatLoadingForDrawables") @SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() { public static Drawable getBackButtonDrawable() {
final int backButtonResource = Utils.getResourceIdentifierOrThrow( final int backButtonResource = Utils.getResourceIdentifierOrThrow(ResourceType.DRAWABLE,
"revanced_settings_toolbar_arrow_left", "drawable"); Utils.appIsUsingBoldIcons()
? "revanced_settings_toolbar_arrow_left_bold"
: "revanced_settings_toolbar_arrow_left");
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource); Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
customizeBackButtonDrawable(drawable); customizeBackButtonDrawable(drawable);
return drawable; return drawable;

View File

@@ -16,6 +16,7 @@ import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ColorPickerPreference; import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference; import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
@@ -38,18 +39,18 @@ public abstract class BaseSearchResultItem {
// Get the corresponding layout resource ID. // Get the corresponding layout resource ID.
public int getLayoutResourceId() { public int getLayoutResourceId() {
return switch (this) { return switch (this) {
case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular"); case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular");
case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch"); case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch");
case LIST -> getResourceIdentifier("revanced_preference_search_result_list"); case LIST -> getResourceIdentifier("revanced_preference_search_result_list");
case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color"); case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color");
case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header"); case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header");
case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result"); case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result");
}; };
} }
private static int getResourceIdentifier(String name) { private static int getResourceIdentifier(String name) {
// Placeholder for actual resource identifier retrieval. // Placeholder for actual resource identifier retrieval.
return Utils.getResourceIdentifierOrThrow(name, "layout"); return Utils.getResourceIdentifierOrThrow(ResourceType.LAYOUT, name);
} }
} }
@@ -75,7 +76,7 @@ public abstract class BaseSearchResultItem {
// Shared method for highlighting text with search query. // Shared method for highlighting text with search query.
protected static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) { protected static CharSequence highlightSearchQuery(CharSequence text, Pattern queryPattern) {
if (TextUtils.isEmpty(text)) return text; if (TextUtils.isEmpty(text) || queryPattern == null) return text;
final int adjustedColor = Utils.adjustColorBrightness( final int adjustedColor = Utils.adjustColorBrightness(
Utils.getAppBackgroundColor(), 0.95f, 1.20f); Utils.getAppBackgroundColor(), 0.95f, 1.20f);
@@ -84,7 +85,10 @@ public abstract class BaseSearchResultItem {
Matcher matcher = queryPattern.matcher(text); Matcher matcher = queryPattern.matcher(text);
while (matcher.find()) { while (matcher.find()) {
spannable.setSpan(highlightSpan, matcher.start(), matcher.end(), int start = matcher.start();
int end = matcher.end();
if (start == end) continue; // Skip zero matches.
spannable.setSpan(highlightSpan, start, end,
SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE); SpannableStringBuilder.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
@@ -224,10 +228,14 @@ public abstract class BaseSearchResultItem {
return searchBuilder.toString(); return searchBuilder.toString();
} }
/**
* Appends normalized searchable text to the builder.
* Uses full Unicode normalization for accurate search across all languages.
*/
private void appendText(StringBuilder builder, CharSequence text) { private void appendText(StringBuilder builder, CharSequence text) {
if (!TextUtils.isEmpty(text)) { if (!TextUtils.isEmpty(text)) {
if (builder.length() > 0) builder.append(" "); if (builder.length() > 0) builder.append(" ");
builder.append(Utils.removePunctuationToLowercase(text)); builder.append(Utils.normalizeTextToLowercase(text));
} }
} }
@@ -272,7 +280,7 @@ public abstract class BaseSearchResultItem {
*/ */
@Override @Override
boolean matchesQuery(String query) { boolean matchesQuery(String query) {
return searchableText.contains(Utils.removePunctuationToLowercase(query)); return searchableText.contains(Utils.normalizeTextToLowercase(query));
} }
/** /**

View File

@@ -1,7 +1,6 @@
package app.revanced.extension.shared.settings.search; package app.revanced.extension.shared.settings.search;
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow; import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
import static app.revanced.extension.shared.settings.search.BaseSearchViewController.DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON;
import android.animation.AnimatorSet; import android.animation.AnimatorSet;
import android.animation.ArgbEvaluator; import android.animation.ArgbEvaluator;
@@ -33,6 +32,7 @@ import java.lang.reflect.Method;
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.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ColorPickerPreference; import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference; import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
@@ -54,15 +54,15 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
protected static final int PAUSE_BETWEEN_BLINKS = 100; protected static final int PAUSE_BETWEEN_BLINKS = 100;
protected static final int ID_PREFERENCE_TITLE = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_TITLE = getResourceIdentifierOrThrow(
"preference_title", "id"); ResourceType.ID, "preference_title");
protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow(
"preference_summary", "id"); ResourceType.ID, "preference_summary");
protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow(
"preference_path", "id"); ResourceType.ID, "preference_path");
protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow(
"preference_switch", "id"); ResourceType.ID, "preference_switch");
protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow( protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow(
"preference_color_dot", "id"); ResourceType.ID, "preference_color_dot");
protected static class RegularViewHolder { protected static class RegularViewHolder {
TextView titleView; TextView titleView;
@@ -275,7 +275,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
holder.titleView.setText(item.highlightedTitle); holder.titleView.setText(item.highlightedTitle);
holder.summaryView.setText(item.highlightedSummary); holder.summaryView.setText(item.highlightedSummary);
holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE); holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE);
holder.iconView.setImageResource(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON); holder.iconView.setImageResource(BaseSearchViewController.getSearchIcon());
} }
/** /**
@@ -484,7 +484,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
return -1; return -1;
} }
for (int i = 0; i < adapter.getCount(); i++) { for (int i = 0, count = adapter.getCount(); i < count; i++) {
Object item = adapter.getItem(i); Object item = adapter.getItem(i);
if (item == targetPreference) { if (item == targetPreference) {
return i; return i;
@@ -522,8 +522,8 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
if (currentAnimator != null && currentAnimator.isRunning()) { if (currentAnimator != null && currentAnimator.isRunning()) {
currentAnimator.cancel(); currentAnimator.cancel();
} }
int startColor = Utils.getAppBackgroundColor(); final int startColor = Utils.getAppBackgroundColor();
int highlightColor = Utils.adjustColorBrightness( final int highlightColor = Utils.adjustColorBrightness(
startColor, startColor,
Utils.isDarkModeEnabled() ? 1.25f : 0.8f Utils.isDarkModeEnabled() ? 1.25f : 0.8f
); );
@@ -566,7 +566,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
} }
// First search on current level. // First search on current level.
for (int i = 0; i < group.getPreferenceCount(); i++) { for (int i = 0, count = group.getPreferenceCount(); i < count; i++) {
Preference pref = group.getPreference(i); Preference pref = group.getPreference(i);
if (key.equals(pref.getKey())) { if (key.equals(pref.getKey())) {
return pref; return pref;

View File

@@ -13,8 +13,8 @@ import android.preference.PreferenceCategory;
import android.preference.PreferenceGroup; import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity; import android.view.Gravity;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
@@ -38,6 +38,7 @@ import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
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;
@@ -45,6 +46,7 @@ import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.preference.ColorPickerPreference; import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference; import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory; import app.revanced.extension.shared.settings.preference.NoTitlePreferenceCategory;
import app.revanced.extension.shared.ui.Dim;
/** /**
* Abstract controller for managing the overlay search view in ReVanced settings. * Abstract controller for managing the overlay search view in ReVanced settings.
@@ -70,14 +72,29 @@ public abstract class BaseSearchViewController {
protected static final int MAX_SEARCH_RESULTS = 50; // Maximum number of search results displayed. protected static final int MAX_SEARCH_RESULTS = 50; // Maximum number of search results displayed.
protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow("revanced_search_view", "id"); protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow(
protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow("revanced_search_view_container", "id"); ResourceType.ID, "revanced_search_view");
protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow("action_search", "id"); protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow(
protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow("revanced_settings_fragments", "id"); ResourceType.ID, "revanced_search_view_container");
public static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON = protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_settings_search_icon", "drawable"); ResourceType.ID, "action_search");
protected static final int MENU_REVANCED_SEARCH_MENU = protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow(
getResourceIdentifierOrThrow("revanced_search_menu", "menu"); ResourceType.ID, "revanced_settings_fragments");
private static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_search_icon");
private static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON_BOLD = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_search_icon_bold");
protected static final int MENU_REVANCED_SEARCH_MENU = getResourceIdentifierOrThrow(
ResourceType.MENU, "revanced_search_menu");
/**
* @return The search icon, either bold or not bold, depending on the ReVanced UI setting.
*/
public static int getSearchIcon() {
return Utils.appIsUsingBoldIcons()
? DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON_BOLD
: DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON;
}
/** /**
* Constructs a new BaseSearchViewController instance. * Constructs a new BaseSearchViewController instance.
@@ -112,7 +129,7 @@ public abstract class BaseSearchViewController {
// Retrieve SearchView and container from XML. // Retrieve SearchView and container from XML.
searchView = activity.findViewById(ID_REVANCED_SEARCH_VIEW); searchView = activity.findViewById(ID_REVANCED_SEARCH_VIEW);
EditText searchEditText = searchView.findViewById(Utils.getResourceIdentifierOrThrow( EditText searchEditText = searchView.findViewById(Utils.getResourceIdentifierOrThrow(
"android:id/search_src_text", null)); null, "android:id/search_src_text"));
// Disable fullscreen keyboard mode. // Disable fullscreen keyboard mode.
searchEditText.setImeOptions(searchEditText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI); searchEditText.setImeOptions(searchEditText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
@@ -123,7 +140,7 @@ public abstract class BaseSearchViewController {
searchView.setQueryHint(str("revanced_settings_search_hint")); searchView.setQueryHint(str("revanced_settings_search_hint"));
// Set text size. // Set text size.
searchEditText.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); searchEditText.setTextSize(16);
// Set cursor color. // Set cursor color.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -149,7 +166,7 @@ public abstract class BaseSearchViewController {
// Create cursor drawable. // Create cursor drawable.
GradientDrawable cursorDrawable = new GradientDrawable(); GradientDrawable cursorDrawable = new GradientDrawable();
cursorDrawable.setShape(GradientDrawable.RECTANGLE); cursorDrawable.setShape(GradientDrawable.RECTANGLE);
cursorDrawable.setSize(Utils.dipToPixels(2), -1); // Width: 2dp, Height: match text height. cursorDrawable.setSize(Dim.dp2, -1); // Width: 2dp, Height: match text height.
cursorDrawable.setColor(cursorColor); cursorDrawable.setColor(cursorColor);
// Set cursor drawable. // Set cursor drawable.
@@ -164,7 +181,7 @@ public abstract class BaseSearchViewController {
overlayContainer = new FrameLayout(activity); overlayContainer = new FrameLayout(activity);
overlayContainer.setVisibility(View.GONE); overlayContainer.setVisibility(View.GONE);
overlayContainer.setBackgroundColor(Utils.getAppBackgroundColor()); overlayContainer.setBackgroundColor(Utils.getAppBackgroundColor());
overlayContainer.setElevation(Utils.dipToPixels(8)); overlayContainer.setElevation(Dim.dp8);
// Container for search results. // Container for search results.
FrameLayout searchResultsContainer = new FrameLayout(activity); FrameLayout searchResultsContainer = new FrameLayout(activity);
@@ -248,6 +265,10 @@ public abstract class BaseSearchViewController {
} }
return false; return false;
}); });
// Set bold icon if needed.
MenuItem search = toolbar.getMenu().findItem(ID_ACTION_SEARCH);
search.setIcon(getSearchIcon());
} }
/** /**
@@ -450,7 +471,7 @@ public abstract class BaseSearchViewController {
filteredSearchItems.clear(); filteredSearchItems.clear();
String queryLower = Utils.removePunctuationToLowercase(query); String queryLower = Utils.normalizeTextToLowercase(query);
Pattern queryPattern = Pattern.compile(Pattern.quote(queryLower), Pattern.CASE_INSENSITIVE); Pattern queryPattern = Pattern.compile(Pattern.quote(queryLower), Pattern.CASE_INSENSITIVE);
// Clear highlighting only for items that were previously visible. // Clear highlighting only for items that were previously visible.
@@ -524,7 +545,7 @@ public abstract class BaseSearchViewController {
noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query)); noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query));
noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary")); noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary"));
noResultsPreference.setSelectable(false); noResultsPreference.setSelectable(false);
noResultsPreference.setIcon(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON); noResultsPreference.setIcon(getSearchIcon());
filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList())); filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList()));
} }
@@ -669,7 +690,7 @@ public abstract class BaseSearchViewController {
protected static GradientDrawable createBackgroundDrawable() { protected static GradientDrawable createBackgroundDrawable() {
GradientDrawable background = new GradientDrawable(); GradientDrawable background = new GradientDrawable();
background.setShape(GradientDrawable.RECTANGLE); background.setShape(GradientDrawable.RECTANGLE);
background.setCornerRadius(Utils.dipToPixels(28)); background.setCornerRadius(Dim.dp28);
background.setColor(getSearchViewBackground()); background.setColor(getSearchViewBackground());
return background; return background;
} }

View File

@@ -24,6 +24,8 @@ import java.util.Deque;
import java.util.LinkedList; import java.util.LinkedList;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.BulletPointPreference; import app.revanced.extension.shared.settings.preference.BulletPointPreference;
import app.revanced.extension.shared.ui.CustomDialog; import app.revanced.extension.shared.ui.CustomDialog;
@@ -37,25 +39,35 @@ public class SearchHistoryManager {
private static final int MAX_HISTORY_SIZE = 5; // Maximum history items stored. private static final int MAX_HISTORY_SIZE = 5; // Maximum history items stored.
private static final int ID_CLEAR_HISTORY_BUTTON = getResourceIdentifierOrThrow( private static final int ID_CLEAR_HISTORY_BUTTON = getResourceIdentifierOrThrow(
"clear_history_button", "id"); ResourceType.ID, "clear_history_button");
private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow( private static final int ID_HISTORY_TEXT = getResourceIdentifierOrThrow(
"history_text", "id"); ResourceType.ID, "history_text");
private static final int ID_HISTORY_ICON = getResourceIdentifierOrThrow(
ResourceType.ID, "history_icon");
private static final int ID_DELETE_ICON = getResourceIdentifierOrThrow( private static final int ID_DELETE_ICON = getResourceIdentifierOrThrow(
"delete_icon", "id"); ResourceType.ID, "delete_icon");
private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow( private static final int ID_EMPTY_HISTORY_TITLE = getResourceIdentifierOrThrow(
"empty_history_title", "id"); ResourceType.ID, "empty_history_title");
private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow( private static final int ID_EMPTY_HISTORY_SUMMARY = getResourceIdentifierOrThrow(
"empty_history_summary", "id"); ResourceType.ID, "empty_history_summary");
private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow( private static final int ID_SEARCH_HISTORY_HEADER = getResourceIdentifierOrThrow(
"search_history_header", "id"); ResourceType.ID, "search_history_header");
private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow( private static final int ID_SEARCH_TIPS_SUMMARY = getResourceIdentifierOrThrow(
"revanced_settings_search_tips_summary", "id"); ResourceType.ID, "revanced_settings_search_tips_summary");
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow( private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN = getResourceIdentifierOrThrow(
"revanced_preference_search_history_screen", "layout"); ResourceType.LAYOUT, "revanced_preference_search_history_screen");
private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow( private static final int LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM = getResourceIdentifierOrThrow(
"revanced_preference_search_history_item", "layout"); ResourceType.LAYOUT, "revanced_preference_search_history_item");
private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow( private static final int ID_SEARCH_HISTORY_LIST = getResourceIdentifierOrThrow(
"search_history_list", "id"); ResourceType.ID, "search_history_list");
private static final int ID_SEARCH_REMOVE_ICON = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_search_remove");
private static final int ID_SEARCH_REMOVE_ICON_BOLD = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_search_remove_bold");
private static final int ID_SEARCH_ARROW_TIME_ICON = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_arrow_time");
private static final int ID_SEARCH_ARROW_TIME_ICON_BOLD = getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, "revanced_settings_arrow_time_bold");
private final Deque<String> searchHistory; private final Deque<String> searchHistory;
private final Activity activity; private final Activity activity;
@@ -97,7 +109,8 @@ public class SearchHistoryManager {
// Inflate search history layout. // Inflate search history layout.
LayoutInflater inflater = LayoutInflater.from(activity); LayoutInflater inflater = LayoutInflater.from(activity);
View historyView = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN, searchHistoryContainer, false); View historyView = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_SCREEN,
searchHistoryContainer, false);
searchHistoryContainer.addView(historyView, new FrameLayout.LayoutParams( searchHistoryContainer.addView(historyView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT)); FrameLayout.LayoutParams.MATCH_PARENT));
@@ -320,17 +333,29 @@ public class SearchHistoryManager {
public void notifyDataSetChanged() { public void notifyDataSetChanged() {
container.removeAllViews(); container.removeAllViews();
for (String query : history) { for (String query : history) {
View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM, container, false); View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM,
container, false);
TextView historyText = view.findViewById(ID_HISTORY_TEXT);
ImageView deleteIcon = view.findViewById(ID_DELETE_ICON);
historyText.setText(query);
// Set click listener for main item (select query). // Set click listener for main item (select query).
view.setOnClickListener(v -> onSelectHistoryItemListener.onSelectHistoryItem(query)); view.setOnClickListener(v -> onSelectHistoryItemListener.onSelectHistoryItem(query));
// Set history icon.
ImageView historyIcon = view.findViewById(ID_HISTORY_ICON);
historyIcon.setImageResource(Utils.appIsUsingBoldIcons()
? ID_SEARCH_ARROW_TIME_ICON_BOLD
: ID_SEARCH_ARROW_TIME_ICON
);
TextView historyText = view.findViewById(ID_HISTORY_TEXT);
historyText.setText(query);
// Set click listener for delete icon. // Set click listener for delete icon.
ImageView deleteIcon = view.findViewById(ID_DELETE_ICON);
deleteIcon.setImageResource(Utils.appIsUsingBoldIcons()
? ID_SEARCH_REMOVE_ICON_BOLD
: ID_SEARCH_REMOVE_ICON
);
deleteIcon.setOnClickListener(v -> createAndShowDialog( deleteIcon.setOnClickListener(v -> createAndShowDialog(
query, query,
str("revanced_settings_search_remove_message"), str("revanced_settings_search_remove_message"),

View File

@@ -54,6 +54,33 @@ public enum ClientType {
ANDROID_VR_1_61_48.supportsMultiAudioTracks, ANDROID_VR_1_61_48.supportsMultiAudioTracks,
"Android VR 1.43" "Android VR 1.43"
), ),
/**
* Video not playable: Paid / Movie / Private / Age-restricted.
* Note: The 'Authorization' key must be excluded from the header.
*
* According to TeamNewPipe in 2022, if the 'androidSdkVersion' field is missing,
* the GVS did not return a valid response:
* [NewPipe#8713 (comment)](https://github.com/TeamNewPipe/NewPipe/issues/8713#issuecomment-1207443550).
*
* According to the latest commit in yt-dlp, the GVS returns a valid response
* even if the 'androidSdkVersion' field is missing:
* [yt-dlp#14693](https://github.com/yt-dlp/yt-dlp/pull/14693).
*
* For some reason, PoToken is not required.
*/
ANDROID_NO_SDK(
3,
"ANDROID",
"",
"",
"",
Build.VERSION.RELEASE,
"20.05.46",
"com.google.android.youtube/20.05.46 (Linux; U; Android " + Build.VERSION.RELEASE + ") gzip",
false,
true,
"Android No SDK"
),
/** /**
* Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children". * Cannot play livestreams and lacks HDR, but can play videos with music and labeled "for children".
* <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a> * <a href="https://dumps.tadiphone.dev/dumps/google/barbet">Google Pixel 9 Pro Fold</a>

View File

@@ -14,19 +14,11 @@ 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.shared.settings.Setting;
import app.revanced.extension.shared.spoof.requests.StreamingDataRequest; import app.revanced.extension.shared.spoof.requests.StreamingDataRequest;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class SpoofVideoStreamsPatch { public class SpoofVideoStreamsPatch {
public static final class AudioStreamLanguageOverrideAvailability implements Setting.Availability {
@Override
public boolean isAvailable() {
return BaseSettings.SPOOF_VIDEO_STREAMS.get() && !preferredClient.useAuth;
}
}
/** /**
* Domain used for internet connectivity verification. * Domain used for internet connectivity verification.
* It has an empty response body and is only used to check for a 204 response code. * It has an empty response body and is only used to check for a 204 response code.
@@ -62,8 +54,7 @@ public class SpoofVideoStreamsPatch {
} }
/** /**
* @param language Language override for non-authenticated requests. If this is null then * @param language Language override for non-authenticated requests.
* {@link BaseSettings#SPOOF_VIDEO_STREAMS_LANGUAGE} is used.
*/ */
public static void setLanguageOverride(@Nullable AppLanguage language) { public static void setLanguageOverride(@Nullable AppLanguage language) {
languageOverride = language; languageOverride = language;
@@ -81,7 +72,7 @@ public class SpoofVideoStreamsPatch {
public static boolean spoofingToClientWithNoMultiAudioStreams() { public static boolean spoofingToClientWithNoMultiAudioStreams() {
return isPatchIncluded() return isPatchIncluded()
&& SPOOF_STREAMING_DATA && SPOOF_STREAMING_DATA
&& preferredClient != ClientType.IPADOS; && !preferredClient.supportsMultiAudioTracks;
} }
/** /**

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.shared.spoof.requests; package app.revanced.extension.shared.spoof.requests;
import static app.revanced.extension.shared.spoof.ClientType.ANDROID_VR_1_43_32;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@@ -13,7 +11,6 @@ import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.requests.Requester; import app.revanced.extension.shared.requests.Requester;
import app.revanced.extension.shared.requests.Route; import app.revanced.extension.shared.requests.Route;
import app.revanced.extension.shared.settings.AppLanguage; import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.spoof.ClientType; import app.revanced.extension.shared.spoof.ClientType;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
@@ -44,7 +41,7 @@ final class PlayerRoutes {
AppLanguage language = SpoofVideoStreamsPatch.getLanguageOverride(); AppLanguage language = SpoofVideoStreamsPatch.getLanguageOverride();
if (language == null) { if (language == null) {
// Force original audio has not overrode the language. // Force original audio has not overrode the language.
language = BaseSettings.SPOOF_VIDEO_STREAMS_LANGUAGE.get(); language = AppLanguage.DEFAULT;
} }
//noinspection ExtractMethodRecommender //noinspection ExtractMethodRecommender
Locale streamLocale = language.getLocale(); Locale streamLocale = language.getLocale();

View File

@@ -16,7 +16,6 @@ import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@@ -83,22 +82,15 @@ public class StreamingDataRequest {
*/ */
private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000; private static final int MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000;
/**
* Cache limit must be greater than the maximum number of videos open at once,
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
* But instead use a much larger value, to handle if a video viewed a while ago
* is somehow still referenced. Each stream is a small array of Strings
* so memory usage is not a concern.
*/
private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap( private static final Map<String, StreamingDataRequest> cache = Collections.synchronizedMap(
new LinkedHashMap<>(100) { Utils.createSizeRestrictedMap(50));
/**
* Cache limit must be greater than the maximum number of videos open at once,
* which theoretically is more than 4 (3 Shorts + one regular minimized video).
* But instead use a much larger value, to handle if a video viewed a while ago
* is somehow still referenced. Each stream is a small array of Strings
* so memory usage is not a concern.
*/
private static final int CACHE_LIMIT = 50;
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
}
});
/** /**
* Strings found in the response if the video is a livestream. * Strings found in the response if the video is a livestream.

View File

@@ -1,7 +1,6 @@
package app.revanced.extension.shared.ui; package app.revanced.extension.shared.ui;
import static app.revanced.extension.shared.Utils.adjustColorBrightness; import static app.revanced.extension.shared.Utils.adjustColorBrightness;
import static app.revanced.extension.shared.Utils.dipToPixels;
import static app.revanced.extension.shared.Utils.getAppBackgroundColor; import static app.revanced.extension.shared.Utils.getAppBackgroundColor;
import static app.revanced.extension.shared.Utils.isDarkModeEnabled; import static app.revanced.extension.shared.Utils.isDarkModeEnabled;
import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.DISABLED_ALPHA; import static app.revanced.extension.shared.settings.preference.ColorPickerPreference.DISABLED_ALPHA;
@@ -13,7 +12,7 @@ import android.view.View;
import androidx.annotation.ColorInt; import androidx.annotation.ColorInt;
public class ColorDot { public class ColorDot {
private static final int STROKE_WIDTH = dipToPixels(1.5f); // Stroke width in dp. private static final int STROKE_WIDTH = Dim.dp(1.5f);
/** /**
* Creates a circular drawable with a main fill and a stroke. * Creates a circular drawable with a main fill and a stroke.
@@ -55,7 +54,7 @@ public class ColorDot {
targetView.setAlpha(enabled ? 1.0f : DISABLED_ALPHA); targetView.setAlpha(enabled ? 1.0f : DISABLED_ALPHA);
if (!isDarkModeEnabled()) { if (!isDarkModeEnabled()) {
targetView.setClipToOutline(true); targetView.setClipToOutline(true);
targetView.setElevation(dipToPixels(2)); targetView.setElevation(Dim.dp2);
} }
} }
} }

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.shared.ui; package app.revanced.extension.shared.ui;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.app.Dialog; import android.app.Dialog;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
@@ -37,7 +35,6 @@ public class CustomDialog {
private final Context context; private final Context context;
private final Dialog dialog; private final Dialog dialog;
private final LinearLayout mainLayout; private final LinearLayout mainLayout;
private final int dip4, dip8, dip16, dip24, dip36;
/** /**
* Creates a custom dialog with a styled layout, including a title, message, buttons, and an optional EditText. * Creates a custom dialog with a styled layout, including a title, message, buttons, and an optional EditText.
@@ -93,13 +90,6 @@ public class CustomDialog {
this.dialog = new Dialog(context); this.dialog = new Dialog(context);
this.dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar. this.dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
// Preset size constants.
dip4 = dipToPixels(4);
dip8 = dipToPixels(8);
dip16 = dipToPixels(16);
dip24 = dipToPixels(24);
dip36 = dipToPixels(36);
// Create main layout. // Create main layout.
mainLayout = createMainLayout(); mainLayout = createMainLayout();
addTitle(title); addTitle(title);
@@ -122,11 +112,11 @@ public class CustomDialog {
private LinearLayout createMainLayout() { private LinearLayout createMainLayout() {
LinearLayout layout = new LinearLayout(context); LinearLayout layout = new LinearLayout(context);
layout.setOrientation(LinearLayout.VERTICAL); layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(dip24, dip16, dip24, dip24); layout.setPadding(Dim.dp24, Dim.dp16, Dim.dp24, Dim.dp24);
// Set rounded rectangle background. // Set rounded rectangle background.
ShapeDrawable background = new ShapeDrawable(new RoundRectShape( ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(28), null, null)); Dim.roundedCorners(28), null, null));
// Dialog background. // Dialog background.
background.getPaint().setColor(Utils.getDialogBackgroundColor()); background.getPaint().setColor(Utils.getDialogBackgroundColor());
layout.setBackground(background); layout.setBackground(background);
@@ -152,7 +142,7 @@ public class CustomDialog {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT); ViewGroup.LayoutParams.WRAP_CONTENT);
params.setMargins(0, 0, 0, dip16); params.setMargins(0, 0, 0, Dim.dp16);
titleView.setLayoutParams(params); titleView.setLayoutParams(params);
mainLayout.addView(titleView); mainLayout.addView(titleView);
@@ -180,9 +170,9 @@ public class CustomDialog {
// EditText (if provided). // EditText (if provided).
if (editText != null) { if (editText != null) {
ShapeDrawable background = new ShapeDrawable(new RoundRectShape( ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(10), null, null)); Dim.roundedCorners(10), null, null));
background.getPaint().setColor(Utils.getEditTextBackground()); background.getPaint().setColor(Utils.getEditTextBackground());
scrollView.setPadding(dip8, dip8, dip8, dip8); scrollView.setPadding(Dim.dp8, Dim.dp8, Dim.dp8, Dim.dp8);
scrollView.setBackground(background); scrollView.setBackground(background);
scrollView.setClipToOutline(true); scrollView.setClipToOutline(true);
@@ -241,7 +231,7 @@ public class CustomDialog {
LinearLayout.LayoutParams buttonContainerParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams buttonContainerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout.LayoutParams.WRAP_CONTENT);
buttonContainerParams.setMargins(0, dip16, 0, 0); buttonContainerParams.setMargins(0, Dim.dp16, 0, 0);
buttonContainer.setLayoutParams(buttonContainerParams); buttonContainer.setLayoutParams(buttonContainerParams);
List<Button> buttons = new ArrayList<>(); List<Button> buttons = new ArrayList<>();
@@ -289,12 +279,12 @@ public class CustomDialog {
button.setEllipsize(TextUtils.TruncateAt.END); button.setEllipsize(TextUtils.TruncateAt.END);
button.setGravity(Gravity.CENTER); button.setGravity(Gravity.CENTER);
// Set internal padding. // Set internal padding.
button.setPadding(dip16, 0, dip16, 0); button.setPadding(Dim.dp16, 0, Dim.dp16, 0);
// Background color for OK button (inversion). // Background color for OK button (inversion).
// Background color for Cancel or Neutral buttons. // Background color for Cancel or Neutral buttons.
ShapeDrawable background = new ShapeDrawable(new RoundRectShape( ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(20), null, null)); Dim.roundedCorners(20), null, null));
background.getPaint().setColor(isOkButton background.getPaint().setColor(isOkButton
? Utils.getOkButtonBackgroundColor() ? Utils.getOkButtonBackgroundColor()
: Utils.getCancelOrNeutralButtonBackgroundColor()); : Utils.getCancelOrNeutralButtonBackgroundColor());
@@ -331,20 +321,19 @@ public class CustomDialog {
if (buttons.isEmpty()) return; if (buttons.isEmpty()) return;
// Check if buttons fit in one row. // Check if buttons fit in one row.
int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
int totalWidth = 0; int totalWidth = 0;
for (Integer width : buttonWidths) { for (Integer width : buttonWidths) {
totalWidth += width; totalWidth += width;
} }
if (buttonWidths.size() > 1) { if (buttonWidths.size() > 1) {
// Add margins for gaps. // Add margins for gaps.
totalWidth += (buttonWidths.size() - 1) * dip8; totalWidth += (buttonWidths.size() - 1) * Dim.dp8;
} }
// Single button: stretch to full width. // Single button: stretch to full width.
if (buttons.size() == 1) { if (buttons.size() == 1) {
layoutSingleButton(buttonContainer, buttons.get(0)); layoutSingleButton(buttonContainer, buttons.get(0));
} else if (totalWidth <= screenWidth * 0.8) { } else if (totalWidth <= Dim.pctWidth(80)) {
// Single row: Neutral, Cancel, OK. // Single row: Neutral, Cancel, OK.
layoutButtonsInRow(buttonContainer, buttons, buttonWidths); layoutButtonsInRow(buttonContainer, buttons, buttonWidths);
} else { } else {
@@ -369,7 +358,7 @@ public class CustomDialog {
button.setLayoutParams(new LinearLayout.LayoutParams( button.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
dip36)); Dim.dp36));
singleContainer.addView(button); singleContainer.addView(button);
buttonContainer.addView(singleContainer); buttonContainer.addView(singleContainer);
} }
@@ -405,17 +394,17 @@ public class CustomDialog {
if (parent != null) parent.removeView(button); if (parent != null) parent.removeView(button);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0, dip36, buttonWidths.get(i)); 0, Dim.dp36, buttonWidths.get(i));
// Set margins based on button type and combination. // Set margins based on button type and combination.
if (buttons.size() == 2) { if (buttons.size() == 2) {
// Neutral + OK or Cancel + OK. // Neutral + OK or Cancel + OK.
params.setMargins(i == 0 ? 0 : dip4, 0, i == 0 ? dip4 : 0, 0); params.setMargins(i == 0 ? 0 : Dim.dp4, 0, i == 0 ? Dim.dp4 : 0, 0);
} else if (buttons.size() == 3) { } else if (buttons.size() == 3) {
// Neutral. // Neutral.
// Cancel. // Cancel.
// OK. // OK.
params.setMargins(i == 0 ? 0 : dip4, 0, i == 2 ? 0 : dip4, 0); params.setMargins(i == 0 ? 0 : Dim.dp4, 0, i == 2 ? 0 : Dim.dp4, 0);
} }
button.setLayoutParams(params); button.setLayoutParams(params);
@@ -447,14 +436,14 @@ public class CustomDialog {
singleContainer.setGravity(Gravity.CENTER); singleContainer.setGravity(Gravity.CENTER);
singleContainer.setLayoutParams(new LinearLayout.LayoutParams( singleContainer.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
dip36)); Dim.dp36));
ViewGroup parent = (ViewGroup) button.getParent(); ViewGroup parent = (ViewGroup) button.getParent();
if (parent != null) parent.removeView(button); if (parent != null) parent.removeView(button);
button.setLayoutParams(new LinearLayout.LayoutParams( button.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
dip36)); Dim.dp36));
singleContainer.addView(button); singleContainer.addView(button);
buttonContainer.addView(singleContainer); buttonContainer.addView(singleContainer);
@@ -463,7 +452,7 @@ public class CustomDialog {
View spacer = new View(context); View spacer = new View(context);
LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
dip8); Dim.dp8);
spacer.setLayoutParams(spacerParams); spacer.setLayoutParams(spacerParams);
buttonContainer.addView(spacer); buttonContainer.addView(spacer);
} }

View File

@@ -0,0 +1,89 @@
package app.revanced.extension.shared.ui;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.util.TypedValue;
/**
* Utility class for converting design units (dp) and screen percentages to pixels.
*/
public final class Dim {
private Dim() {} // Prevent instantiation.
private static final DisplayMetrics METRICS = Resources.getSystem().getDisplayMetrics();
public static final int SCREEN_WIDTH = METRICS.widthPixels;
public static final int SCREEN_HEIGHT = METRICS.heightPixels;
// DP constants (density-independent pixels).
public static final int dp1 = dp(1);
public static final int dp2 = dp(2);
public static final int dp4 = dp(4);
public static final int dp6 = dp(6);
public static final int dp7 = dp(7);
public static final int dp8 = dp(8);
public static final int dp10 = dp(10);
public static final int dp12 = dp(12);
public static final int dp16 = dp(16);
public static final int dp20 = dp(20);
public static final int dp24 = dp(24);
public static final int dp28 = dp(28);
public static final int dp32 = dp(32);
public static final int dp36 = dp(36);
public static final int dp40 = dp(40);
public static final int dp48 = dp(48);
/**
* Converts dp (density-independent pixels) to actual device pixels.
* Uses Android's official TypedValue.applyDimension() for accurate rounding.
*
* @param dp The dp value to convert (supports float, e.g. 1.2f).
* @return The equivalent pixel value as int.
*/
public static int dp(float dp) {
return (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, METRICS);
}
/**
* Converts a percentage of the screen height to pixels.
*
* @param percent The percentage (0100).
* @return The pixel value corresponding to the percentage of screen height.
*/
public static int pctHeight(int percent) {
return (SCREEN_HEIGHT * percent) / 100;
}
/**
* Converts a percentage of the screen width to pixels.
*
* @param percent The percentage (0100).
* @return The pixel value corresponding to the percentage of screen width.
*/
public static int pctWidth(int percent) {
return (SCREEN_WIDTH * percent) / 100;
}
/**
* Converts a percentage of the screen's portrait width (min side) to pixels.
*
* @param percent The percentage (0100).
* @return The pixel value.
*/
public static int pctPortraitWidth(int percent) {
final int portraitWidth = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT);
return (int) (portraitWidth * (percent / 100.0f));
}
/**
* Creates an array of corner radii for a rounded rectangle.
* All corners use the same radius.
*
* @param dp radius in density-independent pixels
* @return array of 8 floats: [top-left-x, top-left-y, top-right-x, top-right-y, ...]
*/
public static float[] roundedCorners(float dp) {
final float r = dp(dp);
return new float[]{r, r, r, r, r, r, r, r};
}
}

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.shared.ui; package app.revanced.extension.shared.ui;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
@@ -63,9 +61,8 @@ public class SheetBottomDialog {
// Add top spacer. // Add top spacer.
View spacer = new View(context); View spacer = new View(context);
final int dip40 = dipToPixels(40);
LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams spacerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, dip40); LinearLayout.LayoutParams.MATCH_PARENT, Dim.dp40);
spacer.setLayoutParams(spacerParams); spacer.setLayoutParams(spacerParams);
spacer.setClickable(true); spacer.setClickable(true);
dragContainer.addView(spacer); dragContainer.addView(spacer);
@@ -105,20 +102,15 @@ public class SheetBottomDialog {
* @return A configured {@link DraggableLinearLayout} with a handle bar and styled background. * @return A configured {@link DraggableLinearLayout} with a handle bar and styled background.
*/ */
public static DraggableLinearLayout createMainLayout(@NonNull Context context, @Nullable Integer backgroundColor) { public static DraggableLinearLayout createMainLayout(@NonNull Context context, @Nullable Integer backgroundColor) {
// Preset size constants.
final int dip4 = dipToPixels(4); // Handle bar height.
final int dip8 = dipToPixels(8); // Dialog padding.
final int dip40 = dipToPixels(40); // Handle bar width.
DraggableLinearLayout mainLayout = new DraggableLinearLayout(context); DraggableLinearLayout mainLayout = new DraggableLinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL); mainLayout.setOrientation(LinearLayout.VERTICAL);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(dip8, 0, dip8, dip8); layoutParams.setMargins(Dim.dp8, 0, Dim.dp8, Dim.dp8);
mainLayout.setLayoutParams(layoutParams); mainLayout.setLayoutParams(layoutParams);
ShapeDrawable background = new ShapeDrawable(new RoundRectShape( ShapeDrawable background = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(12), null, null)); Dim.roundedCorners(12), null, null));
int color = (backgroundColor != null) ? backgroundColor : Utils.getDialogBackgroundColor(); int color = (backgroundColor != null) ? backgroundColor : Utils.getDialogBackgroundColor();
background.getPaint().setColor(color); background.getPaint().setColor(color);
mainLayout.setBackground(background); mainLayout.setBackground(background);
@@ -127,14 +119,14 @@ public class SheetBottomDialog {
LinearLayout handleContainer = new LinearLayout(context); LinearLayout handleContainer = new LinearLayout(context);
LinearLayout.LayoutParams containerParams = new LinearLayout.LayoutParams( LinearLayout.LayoutParams containerParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
containerParams.setMargins(0, dip8, 0, 0); containerParams.setMargins(0, Dim.dp8, 0, 0);
handleContainer.setLayoutParams(containerParams); handleContainer.setLayoutParams(containerParams);
handleContainer.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM); handleContainer.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
View handleBar = new View(context); View handleBar = new View(context);
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape( ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
Utils.createCornerRadii(4), null, null)); Dim.roundedCorners(4), null, null));
handleBackground.getPaint().setColor(Utils.adjustColorBrightness(color, 0.9f, 1.25f)); handleBackground.getPaint().setColor(Utils.adjustColorBrightness(color, 0.9f, 1.25f));
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(dip40, dip4); LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(Dim.dp40, Dim.dp4);
handleBar.setLayoutParams(handleParams); handleBar.setLayoutParams(handleParams);
handleBar.setBackground(handleBackground); handleBar.setBackground(handleBackground);

View File

@@ -1,12 +1,14 @@
package app.revanced.extension.spotify.layout.hide.createbutton; package app.revanced.extension.spotify.layout.hide.createbutton;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter; import app.revanced.extension.spotify.shared.ComponentFilters.ComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter; import app.revanced.extension.spotify.shared.ComponentFilters.ResourceIdComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter; import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
import java.util.List; import java.util.List;
@Deprecated(forRemoval = true)
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class HideCreateButtonPatch { public final class HideCreateButtonPatch {
@@ -15,7 +17,7 @@ public final class HideCreateButtonPatch {
* The main approach used is matching the resource id for the Create button title. * The main approach used is matching the resource id for the Create button title.
*/ */
private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of( private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"), new ResourceIdComponentFilter(ResourceType.STRING, "navigationbar_musicappitems_create_title"),
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded, // 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. // 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. // FIXME: Remove this once the above issue is no longer relevant.
@@ -27,7 +29,7 @@ public final class HideCreateButtonPatch {
* Used in older versions of the app. * Used in older versions of the app.
*/ */
private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER = private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string"); new ResourceIdComponentFilter(ResourceType.STRING, "bottom_navigation_bar_create_tab_title");
/** /**
* 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.

View File

@@ -3,6 +3,7 @@ package app.revanced.extension.spotify.shared;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
public final class ComponentFilters { public final class ComponentFilters {
@@ -19,21 +20,26 @@ public final class ComponentFilters {
public static final class ResourceIdComponentFilter implements ComponentFilter { public static final class ResourceIdComponentFilter implements ComponentFilter {
public final String resourceName; public final String resourceName;
public final String resourceType; public final ResourceType resourceType;
// Android resources are always positive, so -1 is a valid sentinel value to indicate it has not been loaded. // 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. // 0 is returned when a resource has not been found.
private int resourceId = -1; private int resourceId = -1;
@Nullable @Nullable
private String stringfiedResourceId; private String stringfiedResourceId;
@Deprecated
public ResourceIdComponentFilter(String resourceName, String resourceType) { public ResourceIdComponentFilter(String resourceName, String resourceType) {
this(ResourceType.valueOf(resourceType), resourceName);
}
public ResourceIdComponentFilter(ResourceType resourceType, String resourceName) {
this.resourceName = resourceName; this.resourceName = resourceName;
this.resourceType = resourceType; this.resourceType = resourceType;
} }
public int getResourceId() { public int getResourceId() {
if (resourceId == -1) { if (resourceId == -1) {
resourceId = Utils.getResourceIdentifier(resourceName, resourceType); resourceId = Utils.getResourceIdentifier(resourceType, resourceName);
} }
return resourceId; return resourceId;
} }

View File

@@ -0,0 +1,5 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:strava:stub"))
compileOnly(libs.okhttp)
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,217 @@
package app.revanced.extension.strava;
import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import android.webkit.MimeTypeMap;
import app.revanced.extension.shared.ResourceType;
import com.strava.mediamodels.data.MediaType;
import com.strava.photos.data.Media;
import okhttp3.*;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import app.revanced.extension.shared.Utils;
@SuppressLint("NewApi")
public final class AddMediaDownloadPatch {
public static final int ACTION_DOWNLOAD = -1;
public static final int ACTION_OPEN_LINK = -2;
public static final int ACTION_COPY_LINK = -3;
private static final OkHttpClient client = new OkHttpClient();
public static boolean handleAction(int actionId, Media media) {
String url = getUrl(media);
switch (actionId) {
case ACTION_DOWNLOAD:
String name = media.getId();
if (media.getType() == MediaType.VIDEO) {
downloadVideo(url, name);
} else {
downloadPhoto(url, name);
}
return true;
case ACTION_OPEN_LINK:
Utils.openLink(url);
return true;
case ACTION_COPY_LINK:
copyLink(url);
return true;
default:
return false;
}
}
public static void copyLink(CharSequence url) {
Utils.setClipboard(url);
showInfoToast("link_copied_to_clipboard", "🔗");
}
public static void downloadPhoto(String url, String name) {
showInfoToast("loading", "");
Utils.runOnBackgroundThread(() -> {
try (Response response = fetch(url)) {
ResponseBody body = response.body();
String mimeType = body.contentType().toString();
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
ContentResolver resolver = Utils.getContext().getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, name + '.' + extension);
values.put(MediaStore.Images.Media.IS_PENDING, 1);
values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/Strava");
Uri collection = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
? MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
: MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri row = resolver.insert(collection, values);
try (OutputStream outputStream = resolver.openOutputStream(row)) {
transferTo(body.byteStream(), outputStream);
} finally {
values.clear();
values.put(MediaStore.Images.Media.IS_PENDING, 0);
resolver.update(row, values, null);
}
showInfoToast("yis_2024_local_save_image_success", "✔️");
} catch (IOException e) {
showErrorToast("download_failure", "", e);
}
});
}
/**
* Downloads a video in the M3U8 / HLS (HTTP Live Streaming) format.
*/
public static void downloadVideo(String url, String name) {
// The first request yields multiple URLs with different stream options.
// In case of Strava, the first one is always of highest quality.
// Each stream can consist of multiple chunks.
// The second request yields the URLs of all of these chunks.
// Fetch all of them concurrently and pipe their streams into the file in order.
showInfoToast("loading", "");
Utils.runOnBackgroundThread(() -> {
try {
String highestQualityStreamUrl;
try (Response response = fetch(url)) {
highestQualityStreamUrl = replaceFileName(url, lines(response).findFirst().get());
}
List<Future<Response>> futures;
try (Response response = fetch(highestQualityStreamUrl)) {
futures = lines(response)
.map(line -> replaceFileName(highestQualityStreamUrl, line))
.map(chunkUrl -> Utils.submitOnBackgroundThread(() -> fetch(chunkUrl)))
.collect(Collectors.toList());
}
ContentResolver resolver = Utils.getContext().getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.Video.Media.DISPLAY_NAME, name + '.' + "mp4");
values.put(MediaStore.Video.Media.IS_PENDING, 1);
values.put(MediaStore.Video.Media.MIME_TYPE, MimeTypeMap.getSingleton().getMimeTypeFromExtension("mp4"));
values.put(MediaStore.Video.Media.RELATIVE_PATH, Environment.DIRECTORY_MOVIES + "/Strava");
Uri collection = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
? MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
: MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
Uri row = resolver.insert(collection, values);
try (OutputStream outputStream = resolver.openOutputStream(row)) {
Throwable error = null;
for (Future<Response> future : futures) {
if (error != null) {
if (future.cancel(true)) {
continue;
}
}
try (Response response = future.get()) {
if (error == null) {
transferTo(response.body().byteStream(), outputStream);
}
} catch (InterruptedException | IOException e) {
error = e;
} catch (ExecutionException e) {
error = e.getCause();
}
}
if (error != null) {
throw new IOException(error);
}
} finally {
values.clear();
values.put(MediaStore.Video.Media.IS_PENDING, 0);
resolver.update(row, values, null);
}
showInfoToast("yis_2024_local_save_video_success", "✔️");
} catch (IOException e) {
showErrorToast("download_failure", "", e);
}
});
}
private static String getUrl(Media media) {
return media.getType() == MediaType.VIDEO
? ((Media.Video) media).getVideoUrl()
: media.getLargestUrl();
}
private static String getString(String name, String fallback) {
int id = Utils.getResourceIdentifier(ResourceType.STRING, name);
return id != 0
? Utils.getResourceString(id)
: fallback;
}
private static void showInfoToast(String resourceName, String fallback) {
String text = getString(resourceName, fallback);
Utils.showToastShort(text);
}
private static void showErrorToast(String resourceName, String fallback, IOException exception) {
String text = getString(resourceName, fallback);
Utils.showToastLong(text + ' ' + exception.getLocalizedMessage());
}
private static Response fetch(String url) throws IOException {
Request request = new Request.Builder().url(url).build();
Response response = client.newCall(request).execute();
if (!response.isSuccessful()) {
throw new IOException("Got HTTP status code " + response.code());
}
return response;
}
/**
* {@code inputStream.transferTo(outputStream)} is "too new".
*/
private static void transferTo(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024 * 8];
int length;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
}
/**
* Gets all file names.
*/
private static Stream<String> lines(Response response) {
BufferedReader reader = new BufferedReader(response.body().charStream());
return reader.lines().filter(line -> !line.startsWith("#"));
}
private static String replaceFileName(String uri, String newName) {
return uri.substring(0, uri.lastIndexOf('/') + 1) + newName;
}
}

View File

@@ -0,0 +1,227 @@
package app.revanced.extension.strava;
import android.annotation.SuppressLint;
import com.strava.modularframework.data.Destination;
import com.strava.modularframework.data.GenericLayoutModule;
import com.strava.modularframework.data.GenericModuleField;
import com.strava.modularframework.data.ListField;
import com.strava.modularframework.data.ListProperties;
import com.strava.modularframework.data.ModularComponent;
import com.strava.modularframework.data.ModularEntry;
import com.strava.modularframework.data.ModularEntryContainer;
import com.strava.modularframework.data.ModularMenuItem;
import com.strava.modularframework.data.Module;
import com.strava.modularframework.data.MultiStateFieldDescriptor;
import com.strava.modularframeworknetwork.ModularEntryNetworkContainer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@SuppressLint("NewApi")
public class HideDistractionsPatch {
public static boolean upselling;
public static boolean promo;
public static boolean followSuggestions;
public static boolean challengeSuggestions;
public static boolean joinChallenge;
public static boolean joinClub;
public static boolean activityLookback;
public static List<ModularEntry> filterChildrenEntries(ModularEntry modularEntry) {
if (hideModularEntry(modularEntry)) {
return Collections.emptyList();
}
return modularEntry.getChildrenEntries$original().stream()
.filter(childrenEntry -> !hideModularEntry(childrenEntry))
.collect(Collectors.toList());
}
public static List<ModularEntry> filterEntries(ModularEntryContainer modularEntryContainer) {
if (hideModularEntryContainer(modularEntryContainer)) {
return Collections.emptyList();
}
return modularEntryContainer.getEntries$original().stream()
.filter(entry -> !hideModularEntry(entry))
.collect(Collectors.toList());
}
public static List<ModularEntry> filterEntries(ModularEntryNetworkContainer modularEntryNetworkContainer) {
if (hideModularEntryNetworkContainer(modularEntryNetworkContainer)) {
return Collections.emptyList();
}
return modularEntryNetworkContainer.getEntries$original().stream()
.filter(entry -> !hideModularEntry(entry))
.collect(Collectors.toList());
}
public static List<ModularMenuItem> filterMenuItems(ModularEntryContainer modularEntryContainer) {
if (hideModularEntryContainer(modularEntryContainer)) {
return Collections.emptyList();
}
return modularEntryContainer.getMenuItems$original().stream()
.filter(menuItem -> !hideModularMenuItem(menuItem))
.collect(Collectors.toList());
}
public static ListProperties filterProperties(ModularEntryContainer modularEntryContainer) {
if (hideModularEntryContainer(modularEntryContainer)) {
return null;
}
return modularEntryContainer.getProperties$original();
}
public static ListProperties filterProperties(ModularEntryNetworkContainer modularEntryNetworkContainer) {
if (hideModularEntryNetworkContainer(modularEntryNetworkContainer)) {
return null;
}
return modularEntryNetworkContainer.getProperties$original();
}
public static ListField filterField(ListProperties listProperties, String key) {
ListField listField = listProperties.getField$original(key);
if (hideListField(listField)) {
return null;
}
return listField;
}
public static List<ListField> filterFields(ListField listField) {
if (hideListField(listField)) {
return null;
}
return listField.getFields$original().stream()
.filter(field -> !hideListField(field))
.collect(Collectors.toList());
}
public static List<Module> filterModules(ModularEntry modularEntry) {
if (hideModularEntry(modularEntry)) {
return Collections.emptyList();
}
return modularEntry.getModules$original().stream()
.filter(module -> !hideModule(module))
.collect(Collectors.toList());
}
public static GenericModuleField filterField(GenericLayoutModule genericLayoutModule, String key) {
if (hideGenericLayoutModule(genericLayoutModule)) {
return null;
}
GenericModuleField field = genericLayoutModule.getField$original(key);
if (hideGenericModuleField(field)) {
return null;
}
return field;
}
public static GenericModuleField[] filterFields(GenericLayoutModule genericLayoutModule) {
if (hideGenericLayoutModule(genericLayoutModule)) {
return new GenericModuleField[0];
}
return Arrays.stream(genericLayoutModule.getFields$original())
.filter(field -> !hideGenericModuleField(field))
.toArray(GenericModuleField[]::new);
}
public static GenericLayoutModule[] filterSubmodules(GenericLayoutModule genericLayoutModule) {
if (hideGenericLayoutModule(genericLayoutModule)) {
return new GenericLayoutModule[0];
}
return Arrays.stream(genericLayoutModule.getSubmodules$original())
.filter(submodule -> !hideGenericLayoutModule(submodule))
.toArray(GenericLayoutModule[]::new);
}
public static List<Module> filterSubmodules(ModularComponent modularComponent) {
if (hideByName(modularComponent.getPage()) || hideByName(modularComponent.getElement())) {
return Collections.emptyList();
}
return modularComponent.getSubmodules$original().stream()
.filter(submodule -> !hideModule(submodule))
.collect(Collectors.toList());
}
public static Map<String, GenericModuleField> filterStateMap(MultiStateFieldDescriptor multiStateFieldDescriptor) {
return multiStateFieldDescriptor.getStateMap$original().entrySet().stream()
.filter(entry -> !hideGenericModuleField(entry.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
private static boolean hideModule(Module module) {
return module == null ||
hideByName(module.getPage()) ||
hideByName(module.getElement());
}
private static boolean hideModularEntry(ModularEntry modularEntry) {
return modularEntry == null ||
hideByName(modularEntry.getPage()) ||
hideByName(modularEntry.getElement()) ||
hideByDestination(modularEntry.getDestination());
}
private static boolean hideGenericLayoutModule(GenericLayoutModule genericLayoutModule) {
try {
return genericLayoutModule == null ||
hideByName(genericLayoutModule.getPage()) ||
hideByName(genericLayoutModule.getElement()) ||
hideByDestination(genericLayoutModule.getDestination());
} catch (RuntimeException getParentEntryOrThrowException) {
return false;
}
}
private static boolean hideListField(ListField listField) {
return listField == null ||
hideByName(listField.getElement()) ||
hideByDestination(listField.getDestination());
}
private static boolean hideGenericModuleField(GenericModuleField genericModuleField) {
return genericModuleField == null ||
hideByName(genericModuleField.getElement()) ||
hideByDestination(genericModuleField.getDestination());
}
private static boolean hideModularEntryContainer(ModularEntryContainer modularEntryContainer) {
return modularEntryContainer == null ||
hideByName(modularEntryContainer.getPage());
}
private static boolean hideModularEntryNetworkContainer(ModularEntryNetworkContainer modularEntryNetworkContainer) {
return modularEntryNetworkContainer == null ||
hideByName(modularEntryNetworkContainer.getPage());
}
private static boolean hideModularMenuItem(ModularMenuItem modularMenuItem) {
return modularMenuItem == null ||
hideByName(modularMenuItem.getElementName()) ||
hideByDestination(modularMenuItem.getDestination());
}
private static boolean hideByName(String name) {
return name != null && (
upselling && name.contains("_upsell") ||
promo && (name.equals("promo") || name.equals("top_of_tab_promo")) ||
followSuggestions && name.equals("suggested_follows") ||
challengeSuggestions && name.equals("suggested_challenges") ||
joinChallenge && name.equals("challenge") ||
joinClub && name.equals("club") ||
activityLookback && name.equals("highlighted_activity_lookback")
);
}
private static boolean hideByDestination(Destination destination) {
if (destination == null) {
return false;
}
String url = destination.getUrl();
return url != null && (
upselling && url.startsWith("strava://subscription/checkout")
);
}
}

View File

@@ -0,0 +1,12 @@
plugins {
alias(libs.plugins.android.library)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 21
}
}

View File

@@ -0,0 +1 @@
<manifest/>

View File

@@ -0,0 +1,15 @@
package com.strava.mediamodels.data;
import java.io.Serializable;
public interface MediaContent extends Serializable {
String getCaption();
String getId();
String getReferenceId();
MediaType getType();
void setCaption(String caption);
}

View File

@@ -0,0 +1,44 @@
package com.strava.mediamodels.data;
import java.io.Serializable;
public final class MediaDimension implements Comparable<MediaDimension>, Serializable {
private final int height;
private final int width;
public MediaDimension(int width, int height) {
this.width = width;
this.height = height;
}
public int getHeight() {
return height;
}
public float getHeightScale() {
if (width <= 0 || height <= 0) {
return 1f;
}
return height / width;
}
public int getWidth() {
return width;
}
public float getWidthScale() {
if (width <= 0 || height <= 0) {
return 1f;
}
return width / height;
}
public boolean isLandscape() {
return width > 0 && width >= height;
}
@Override
public int compareTo(MediaDimension other) {
return 0;
}
}

View File

@@ -0,0 +1,16 @@
package com.strava.mediamodels.data;
public enum MediaType {
PHOTO(1),
VIDEO(2);
private final int remoteValue;
private MediaType(int remoteValue) {
this.remoteValue = remoteValue;
}
public int getRemoteValue() {
return remoteValue;
}
}

View File

@@ -0,0 +1,17 @@
package com.strava.mediamodels.data;
import java.util.SortedMap;
public interface RemoteMediaContent extends MediaContent {
MediaDimension getLargestSize();
String getLargestUrl();
SortedMap<Integer, MediaDimension> getSizes();
String getSmallestUrl();
RemoteMediaStatus getStatus();
SortedMap<Integer, String> getUrls();
}

View File

@@ -0,0 +1,11 @@
package com.strava.mediamodels.data;
public enum RemoteMediaStatus {
NEW,
PENDING,
PROCESSED,
REPORTED,
REINSTATED,
DELETED,
FAILED
}

View File

@@ -0,0 +1,7 @@
package com.strava.modularframework.data;
import java.io.Serializable;
public abstract class Destination implements Serializable {
public abstract String getUrl();
}

View File

@@ -0,0 +1,28 @@
package com.strava.modularframework.data;
import java.io.Serializable;
public abstract class GenericLayoutModule implements Serializable, Module {
public abstract Destination getDestination();
@Override
public abstract String getElement();
public abstract GenericModuleField getField(String key);
// Added by patch.
public abstract GenericModuleField getField$original(String key);
public abstract GenericModuleField[] getFields();
// Added by patch.
public abstract GenericModuleField[] getFields$original();
@Override
public abstract String getPage();
public abstract GenericLayoutModule[] getSubmodules();
// Added by patch.
public abstract GenericLayoutModule[] getSubmodules$original();
}

View File

@@ -0,0 +1,9 @@
package com.strava.modularframework.data;
import java.io.Serializable;
public abstract class GenericModuleField implements Serializable {
public abstract Destination getDestination();
public abstract String getElement();
}

View File

@@ -0,0 +1,14 @@
package com.strava.modularframework.data;
import java.util.List;
public abstract class ListField {
public abstract Destination getDestination();
public abstract String getElement();
public abstract List<ListField> getFields();
// Added by patch.
public abstract List<ListField> getFields$original();
}

View File

@@ -0,0 +1,8 @@
package com.strava.modularframework.data;
public abstract class ListProperties {
public abstract ListField getField(String key);
// Added by patch.
public abstract ListField getField$original(String key);
}

View File

@@ -0,0 +1,16 @@
package com.strava.modularframework.data;
import java.util.List;
public abstract class ModularComponent implements Module {
@Override
public abstract String getElement();
@Override
public abstract String getPage();
public abstract List<Module> getSubmodules();
// Added by patch.
public abstract List<Module> getSubmodules$original();
}

View File

@@ -0,0 +1,21 @@
package com.strava.modularframework.data;
import java.util.List;
public interface ModularEntry {
List<ModularEntry> getChildrenEntries();
// Added by patch.
List<ModularEntry> getChildrenEntries$original();
Destination getDestination();
String getElement();
List<Module> getModules();
// Added by patch.
List<Module> getModules$original();
String getPage();
}

View File

@@ -0,0 +1,22 @@
package com.strava.modularframework.data;
import java.util.List;
public abstract class ModularEntryContainer {
public abstract List<ModularEntry> getEntries();
// Added by patch.
public abstract List<ModularEntry> getEntries$original();
public abstract List<ModularMenuItem> getMenuItems();
// Added by patch.
public abstract List<ModularMenuItem> getMenuItems$original();
public abstract String getPage();
public abstract ListProperties getProperties();
// Added by patch.
public abstract ListProperties getProperties$original();
}

View File

@@ -0,0 +1,7 @@
package com.strava.modularframework.data;
public abstract class ModularMenuItem {
public abstract Destination getDestination();
public abstract String getElementName();
}

View File

@@ -0,0 +1,7 @@
package com.strava.modularframework.data;
public interface Module {
String getElement();
String getPage();
}

View File

@@ -0,0 +1,10 @@
package com.strava.modularframework.data;
import java.util.Map;
public abstract class MultiStateFieldDescriptor {
public abstract Map<String, GenericModuleField> getStateMap();
// Added by patch.
public abstract Map<String, GenericModuleField> getStateMap$original();
}

View File

@@ -0,0 +1,19 @@
package com.strava.modularframeworknetwork;
import com.strava.modularframework.data.ListProperties;
import com.strava.modularframework.data.ModularEntry;
import java.util.List;
public abstract class ModularEntryNetworkContainer {
public abstract List<ModularEntry> getEntries();
// Added by patch.
public abstract List<ModularEntry> getEntries$original();
public abstract String getPage();
public abstract ListProperties getProperties();
// Added by patch.
public abstract ListProperties getProperties$original();
}

View File

@@ -0,0 +1,287 @@
package com.strava.photos.data;
import com.strava.mediamodels.data.MediaDimension;
import com.strava.mediamodels.data.MediaType;
import com.strava.mediamodels.data.RemoteMediaContent;
import com.strava.mediamodels.data.RemoteMediaStatus;
import java.util.SortedMap;
public abstract class Media implements RemoteMediaContent {
public static final class Photo extends Media {
private final Long activityId;
private final String activityName;
private final long athleteId;
private String caption;
private final String createdAt;
private final String createdAtLocal;
private final String cursor;
private final String id;
private final SortedMap<Integer, MediaDimension> sizes;
private final RemoteMediaStatus status;
private final String tag;
private final MediaType type;
private final SortedMap<Integer, String> urls;
@Override
public Long getActivityId() {
return activityId;
}
@Override
public String getActivityName() {
return activityName;
}
@Override
public long getAthleteId() {
return athleteId;
}
@Override
public String getCaption() {
return caption;
}
@Override
public String getCreatedAt() {
return createdAt;
}
@Override
public String getCreatedAtLocal() {
return createdAtLocal;
}
@Override
public String getCursor() {
return cursor;
}
@Override
public String getId() {
return id;
}
@Override
public SortedMap<Integer, MediaDimension> getSizes() {
return sizes;
}
@Override
public RemoteMediaStatus getStatus() {
return status;
}
@Override
public String getTag() {
return tag;
}
@Override
public MediaType getType() {
return type;
}
@Override
public SortedMap<Integer, String> getUrls() {
return urls;
}
@Override
public void setCaption(String caption) {
this.caption = caption;
}
public Photo(String id,
String caption,
SortedMap<Integer, String> urls,
SortedMap<Integer, MediaDimension> sizes,
long athleteId,
String createdAt,
String createdAtLocal,
Long activityId,
String activityName,
RemoteMediaStatus status,
String tag,
String cursor) {
this.id = id;
this.caption = caption;
this.urls = urls;
this.sizes = sizes;
this.athleteId = athleteId;
this.createdAt = createdAt;
this.createdAtLocal = createdAtLocal;
this.activityId = activityId;
this.activityName = activityName;
this.status = status;
this.tag = tag;
this.cursor = cursor;
this.type = MediaType.PHOTO;
}
}
public static final class Video extends Media {
private final Long activityId;
private final String activityName;
private final long athleteId;
private String caption;
private final String createdAt;
private final String createdAtLocal;
private final String cursor;
private final Float durationSeconds;
private final String id;
private final SortedMap<Integer, MediaDimension> sizes;
private final RemoteMediaStatus status;
private final String tag;
private final MediaType type;
private final SortedMap<Integer, String> urls;
private final String videoUrl;
@Override
public Long getActivityId() {
return activityId;
}
@Override
public String getActivityName() {
return activityName;
}
@Override
public long getAthleteId() {
return athleteId;
}
@Override
public String getCaption() {
return caption;
}
@Override
public String getCreatedAt() {
return createdAt;
}
@Override
public String getCreatedAtLocal() {
return createdAtLocal;
}
@Override
public String getCursor() {
return cursor;
}
public final Float getDurationSeconds() {
return durationSeconds;
}
@Override
public String getId() {
return id;
}
@Override
public SortedMap<Integer, MediaDimension> getSizes() {
return sizes;
}
@Override
public RemoteMediaStatus getStatus() {
return status;
}
@Override
public String getTag() {
return tag;
}
@Override
public MediaType getType() {
return type;
}
@Override
public SortedMap<Integer, String> getUrls() {
return urls;
}
public final String getVideoUrl() {
return videoUrl;
}
@Override
public void setCaption(String caption) {
this.caption = caption;
}
public Video(String id,
String caption,
SortedMap<Integer, String> urls,
SortedMap<Integer, MediaDimension> sizes,
long athleteId,
String createdAt,
String createdAtLocal,
Long activityId,
String activityName,
RemoteMediaStatus status,
String videoUrl,
Float durationSeconds,
String tag,
String cursor) {
this.id = id;
this.caption = caption;
this.urls = urls;
this.sizes = sizes;
this.athleteId = athleteId;
this.createdAt = createdAt;
this.createdAtLocal = createdAtLocal;
this.activityId = activityId;
this.activityName = activityName;
this.status = status;
this.videoUrl = videoUrl;
this.durationSeconds = durationSeconds;
this.tag = tag;
this.cursor = cursor;
this.type = MediaType.VIDEO;
}
}
public abstract Long getActivityId();
public abstract String getActivityName();
public abstract long getAthleteId();
public abstract String getCreatedAt();
public abstract String getCreatedAtLocal();
public abstract String getCursor();
@Override
public MediaDimension getLargestSize() {
return null;
}
@Override
public String getLargestUrl() {
return null;
}
@Override
public String getReferenceId() {
return null;
}
@Override
public String getSmallestUrl() {
return null;
}
public abstract String getTag();
private Media() {
}
}

View File

@@ -23,6 +23,12 @@ public class ExtensionPreferenceCategory extends ConditionalPreferenceCategory {
public void addPreferences(Context context) { public void addPreferences(Context context) {
addPreference(new ReVancedTikTokAboutPreference(context)); addPreference(new ReVancedTikTokAboutPreference(context));
addPreference(new TogglePreference(context,
"Sanitize sharing links",
"Remove tracking parameters from shared links.",
BaseSettings.SANITIZE_SHARED_LINKS
));
addPreference(new TogglePreference(context, addPreference(new TogglePreference(context,
"Enable debug log", "Enable debug log",
"Show extension debug log.", "Show extension debug log.",

View File

@@ -0,0 +1,29 @@
package app.revanced.extension.tiktok.share;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.privacy.LinkSanitizer;
import app.revanced.extension.shared.settings.BaseSettings;
@SuppressWarnings("unused")
public final class ShareUrlSanitizer {
private static final LinkSanitizer sanitizer = new LinkSanitizer();
/**
* Injection point for setting check.
*/
public static boolean shouldSanitize() {
return BaseSettings.SANITIZE_SHARED_LINKS.get();
}
/**
* Injection point for URL sanitization.
*/
public static String sanitizeShareUrl(final String url) {
if (url == null || url.isEmpty()) {
return url;
}
return sanitizer.sanitizeUrlString(url);
}
}

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