Compare commits

..

187 Commits

Author SHA1 Message Date
oSumAtrIX
29699557aa some more progress 2026-01-21 21:02:51 +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
5a15476a9f fix names 2026-01-14 02:31:53 +01:00
oSumAtrIX
50f95543f1 fix 2026-01-14 02:23:45 +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
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
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
oSumAtrIX
cceb7a7c43 update to new options API 2026-01-06 16:24:54 +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
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
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
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
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
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
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
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
LisoUseInAIKyrios
0855290097 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-11-08 14:29:22 +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
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
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
LisoUseInAIKyrios
fc70f852f9 Merge remote-tracking branch 'upstream/dev' into feat/patcher_instruction_filters 2025-11-02 09:46:22 +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
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
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
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
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
804 changed files with 67994 additions and 69994 deletions

View File

@@ -25,8 +25,7 @@ jobs:
- name: Build
env:
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ env.GITHUB_ACTOR }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid --no-daemon
- name: Upload artifacts

View File

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

View File

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

View File

@@ -31,8 +31,7 @@ jobs:
- name: Build
env:
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew :patches:buildAndroid clean
- name: Setup Node.js
@@ -56,8 +55,6 @@ jobs:
id: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
- name: Attest
if: steps.release.outputs.new_release_published == 'true'

View File

@@ -1,103 +1,3 @@
# [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))
* **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)

View File

@@ -11,6 +11,7 @@ import android.widget.Toolbar;
import app.revanced.extension.music.settings.preference.MusicPreferenceFragment;
import app.revanced.extension.music.settings.search.MusicSearchViewController;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
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.
// Custom style located in resources/music/values/style.xml
activity.setTheme(Utils.getResourceIdentifierOrThrow(
"Theme.ReVanced.YouTubeMusic.Settings", "style"));
}
/**
* Returns the resource ID for the YouTube Music settings layout.
*/
@Override
protected int getContentViewResourceId() {
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
ResourceType.STYLE, "Theme.ReVanced.YouTubeMusic.Settings"));
}
/**

View File

@@ -4,12 +4,12 @@ import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.EnumSetting;
import app.revanced.extension.shared.spoof.ClientType;
public class Settings extends YouTubeAndMusicSettings {
public class Settings extends BaseSettings {
// Ads
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);

View File

@@ -1,15 +0,0 @@
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

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

View File

@@ -1,590 +0,0 @@
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

@@ -1,17 +0,0 @@
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

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

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

@@ -32,7 +32,11 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import android.widget.Toolbar;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
@@ -43,8 +47,10 @@ import java.text.Collator;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
@@ -76,6 +82,8 @@ public class Utils {
@Nullable
private static Boolean isDarkModeEnabled;
private static boolean appIsUsingBoldIcons;
// Cached Collator instance with its locale.
@Nullable
private static Locale cachedCollatorLocale;
@@ -148,12 +156,12 @@ public class Utils {
/**
* 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.
*/
public static void hideViewBy0dpUnderCondition(BooleanSetting condition, View view) {
if (hideViewBy0dpUnderCondition(condition.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition);
public static void hideViewBy0dpUnderCondition(BooleanSetting setting, View view) {
if (hideViewBy0dpUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + setting);
}
}
@@ -165,22 +173,47 @@ public class Utils {
*/
public static boolean hideViewBy0dpUnderCondition(boolean condition, View view) {
if (condition) {
hideViewByLayoutParams(view);
hideViewBy0dp(view);
return true;
}
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.
*
* @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.
*/
public static void hideViewUnderCondition(BooleanSetting condition, View view) {
if (hideViewUnderCondition(condition.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition);
public static void hideViewUnderCondition(BooleanSetting setting, View view) {
if (hideViewUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + setting);
}
}
@@ -199,14 +232,14 @@ public class Utils {
return false;
}
public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting condition, View view) {
if (hideViewByRemovingFromParentUnderCondition(condition.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + condition);
public static void hideViewByRemovingFromParentUnderCondition(BooleanSetting setting, View view) {
if (hideViewByRemovingFromParentUnderCondition(setting.get(), view)) {
Logger.printDebug(() -> "View hidden by setting: " + setting);
}
}
public static boolean hideViewByRemovingFromParentUnderCondition(boolean setting, View view) {
if (setting) {
public static boolean hideViewByRemovingFromParentUnderCondition(boolean condition, View view) {
if (condition) {
ViewParent parent = view.getParent();
if (parent instanceof ViewGroup parentGroup) {
parentGroup.removeView(view);
@@ -278,12 +311,13 @@ public class Utils {
* @return zero, if the resource is not found.
*/
@SuppressLint("DiscouragedApi")
public static int getResourceIdentifier(Context context, String resourceIdentifierName, @Nullable String type) {
return context.getResources().getIdentifier(resourceIdentifierName, type, context.getPackageName());
public static int getResourceIdentifier(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
return context.getResources().getIdentifier(resourceIdentifierName,
type == null ? null : type.value, context.getPackageName());
}
public static int getResourceIdentifierOrThrow(Context context, String resourceIdentifierName, @Nullable String type) {
final int resourceId = getResourceIdentifier(context, resourceIdentifierName, type);
public static int getResourceIdentifierOrThrow(Context context, @Nullable ResourceType type, String resourceIdentifierName) {
final int resourceId = getResourceIdentifier(context, type, resourceIdentifierName);
if (resourceId == 0) {
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
+ " type: " + type);
@@ -293,52 +327,44 @@ public class Utils {
/**
* @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) {
return getResourceIdentifier(getContext(), resourceIdentifierName, type);
public static int getResourceIdentifier(@Nullable ResourceType type, String resourceIdentifierName) {
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) {
final int resourceId = getResourceIdentifier(getContext(), resourceIdentifierName, type);
if (resourceId == 0) {
throw new Resources.NotFoundException("No resource id exists with name: " + resourceIdentifierName
+ " type: " + type);
}
return resourceId;
}
public static String getResourceString(int id) throws Resources.NotFoundException {
return getContext().getResources().getString(id);
public static int getResourceIdentifierOrThrow(@Nullable ResourceType type, String resourceIdentifierName) {
return getResourceIdentifierOrThrow(getContext(), type, resourceIdentifierName);
}
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 {
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(resourceIdentifierName, "anim"));
return AnimationUtils.loadAnimation(getContext(), getResourceIdentifierOrThrow(ResourceType.ANIM, resourceIdentifierName));
}
@ColorInt
public static int getResourceColor(String resourceIdentifierName) throws Resources.NotFoundException {
//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 {
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(resourceIdentifierName, "dimen"));
return getContext().getResources().getDimensionPixelSize(getResourceIdentifierOrThrow(ResourceType.DIMEN, resourceIdentifierName));
}
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 {
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(resourceIdentifierName, "array"));
return getContext().getResources().getStringArray(getResourceIdentifierOrThrow(ResourceType.ARRAY, resourceIdentifierName));
}
public interface MatchFilter<T> {
@@ -349,7 +375,7 @@ public class Utils {
* Includes sub children.
*/
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
return (R) child;
}
@@ -806,6 +832,21 @@ public class Utils {
window.setBackgroundDrawable(null); // Remove default dialog background
}
/**
* @return If the unpatched app is currently using bold icons.
*/
public static boolean appIsUsingBoldIcons() {
return appIsUsingBoldIcons;
}
/**
* 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;
}
/**
* Sets the theme light color used by the app.
*/
@@ -1167,4 +1208,18 @@ public class Utils {
public static float clamp(float value, float lower, float 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 app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.ui.CustomDialog;
@@ -128,7 +129,7 @@ abstract class Check {
// Add icon to the dialog.
ImageView iconView = new ImageView(activity);
iconView.setImageResource(Utils.getResourceIdentifierOrThrow(
"revanced_ic_dialog_alert", "drawable"));
ResourceType.DRAWABLE, "revanced_ic_dialog_alert"));
iconView.setColorFilter(Utils.getAppForegroundColor(), PorterDuff.Mode.SRC_IN);
iconView.setPadding(0, 0, 0, 0);
LinearLayout.LayoutParams iconParams = new LinearLayout.LayoutParams(

View File

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

View File

@@ -13,6 +13,7 @@ import java.util.Locale;
import app.revanced.extension.shared.GmsCoreSupport;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
@@ -65,7 +66,7 @@ public class CustomBrandingPatch {
iconName += "_custom";
}
notificationSmallIcon = Utils.getResourceIdentifier(iconName, "drawable");
notificationSmallIcon = Utils.getResourceIdentifier(ResourceType.DRAWABLE, iconName);
if (notificationSmallIcon == 0) {
Logger.printException(() -> "Could not load notification small icon");
}

View File

@@ -1,213 +0,0 @@
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;
protected 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

@@ -13,6 +13,7 @@ import android.widget.TextView;
import android.widget.Toolbar;
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.ToolbarPreferenceFragment;
import app.revanced.extension.shared.ui.Dim;
@@ -25,13 +26,13 @@ import app.revanced.extension.shared.ui.Dim;
public abstract class BaseActivityHook extends Activity {
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 =
getResourceIdentifierOrThrow("revanced_toolbar_parent", "id");
getResourceIdentifierOrThrow(ResourceType.ID, "revanced_toolbar_parent");
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 =
getResourceIdentifierOrThrow("revanced_settings_title", "string");
getResourceIdentifierOrThrow(ResourceType.STRING, "revanced_settings_title");
/**
* Layout parameters for the toolbar, extracted from the dummy toolbar.
@@ -123,16 +124,18 @@ public abstract class BaseActivityHook extends Activity {
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.
*/
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.
*/

View File

@@ -5,6 +5,8 @@ import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.patches.CustomBrandingPatch.BrandingTheme;
import static app.revanced.extension.shared.settings.Setting.parent;
import app.revanced.extension.shared.Logger;
/**
* Settings shared across multiple apps.
* <p>
@@ -24,10 +26,19 @@ 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.
*/
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 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.
//
@@ -44,4 +55,13 @@ public class BaseSettings {
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

@@ -1,14 +0,0 @@
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 app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
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.
*/
protected void initialize() {
String preferenceResourceName = BaseSettings.SHOW_MENU_ICONS.get()
? "revanced_prefs_icons"
: "revanced_prefs";
final var identifier = Utils.getResourceIdentifier(preferenceResourceName, "xml");
String preferenceResourceName;
if (BaseSettings.SHOW_MENU_ICONS.get()) {
preferenceResourceName = Utils.appIsUsingBoldIcons()
? "revanced_prefs_icons_bold"
: "revanced_prefs_icons";
} else {
preferenceResourceName = "revanced_prefs";
}
final var identifier = Utils.getResourceIdentifier(ResourceType.XML, preferenceResourceName);
if (identifier == 0) return;
addPreferencesFromResource(identifier);

View File

@@ -31,6 +31,7 @@ import java.util.Locale;
import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting;
@@ -81,13 +82,13 @@ public class ColorPickerPreference extends EditTextPreference {
private boolean opacitySliderEnabled = false;
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 =
getResourceIdentifierOrThrow("preference_color_dot", "id");
getResourceIdentifierOrThrow(ResourceType.ID, "preference_color_dot");
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 =
getResourceIdentifierOrThrow("revanced_color_picker", "layout");
getResourceIdentifierOrThrow(ResourceType.LAYOUT, "revanced_color_picker");
/**
* Removes non valid hex characters, converts to all uppercase,

View File

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

View File

@@ -38,6 +38,7 @@ 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;
@@ -52,25 +53,26 @@ import app.revanced.extension.shared.ui.Dim;
public class FeatureFlagsManagerPreference extends Preference {
private static final int DRAWABLE_REVANCED_SETTINGS_SELECT_ALL =
getResourceIdentifierOrThrow("revanced_settings_select_all", "drawable");
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_select_all");
private static final int DRAWABLE_REVANCED_SETTINGS_DESELECT_ALL =
getResourceIdentifierOrThrow("revanced_settings_deselect_all", "drawable");
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_deselect_all");
private static final int DRAWABLE_REVANCED_SETTINGS_COPY_ALL =
getResourceIdentifierOrThrow("revanced_settings_copy_all", "drawable");
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_copy_all");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_ONE =
getResourceIdentifierOrThrow("revanced_settings_arrow_right_one", "drawable");
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_one");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_RIGHT_DOUBLE =
getResourceIdentifierOrThrow("revanced_settings_arrow_right_double", "drawable");
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_right_double");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_ONE =
getResourceIdentifierOrThrow("revanced_settings_arrow_left_one", "drawable");
getResourceIdentifierOrThrow(ResourceType.DRAWABLE, "revanced_settings_arrow_left_one");
private static final int DRAWABLE_REVANCED_SETTINGS_ARROW_LEFT_DOUBLE =
getResourceIdentifierOrThrow("revanced_settings_arrow_left_double", "drawable");
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.
45386834L, // 'You' tab settings icon.
45685201L // Bold icons. Forcing off interferes with patch changes and YT icons are broken.
);
/**

View File

@@ -17,9 +17,11 @@ import android.widget.Toolbar;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook;
import app.revanced.extension.shared.ui.Dim;
import app.revanced.extension.shared.settings.BaseSettings;
@SuppressWarnings({"deprecation", "NewApi"})
public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
@@ -133,8 +135,10 @@ public class ToolbarPreferenceFragment extends AbstractPreferenceFragment {
*/
@SuppressLint("UseCompatLoadingForDrawables")
public static Drawable getBackButtonDrawable() {
final int backButtonResource = Utils.getResourceIdentifierOrThrow(
"revanced_settings_toolbar_arrow_left", "drawable");
final int backButtonResource = Utils.getResourceIdentifierOrThrow(ResourceType.DRAWABLE,
Utils.appIsUsingBoldIcons()
? "revanced_settings_toolbar_arrow_left_bold"
: "revanced_settings_toolbar_arrow_left");
Drawable drawable = Utils.getContext().getResources().getDrawable(backButtonResource);
customizeBackButtonDrawable(drawable);
return drawable;

View File

@@ -16,6 +16,7 @@ import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.preference.ColorPickerPreference;
import app.revanced.extension.shared.settings.preference.CustomDialogListPreference;
@@ -38,18 +39,18 @@ public abstract class BaseSearchResultItem {
// Get the corresponding layout resource ID.
public int getLayoutResourceId() {
return switch (this) {
case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular");
case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch");
case LIST -> getResourceIdentifier("revanced_preference_search_result_list");
case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color");
case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header");
case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result");
case REGULAR, URL_LINK -> getResourceIdentifier("revanced_preference_search_result_regular");
case SWITCH -> getResourceIdentifier("revanced_preference_search_result_switch");
case LIST -> getResourceIdentifier("revanced_preference_search_result_list");
case COLOR_PICKER -> getResourceIdentifier("revanced_preference_search_result_color");
case GROUP_HEADER -> getResourceIdentifier("revanced_preference_search_result_group_header");
case NO_RESULTS -> getResourceIdentifier("revanced_preference_search_no_result");
};
}
private static int getResourceIdentifier(String name) {
// Placeholder for actual resource identifier retrieval.
return Utils.getResourceIdentifierOrThrow(name, "layout");
return Utils.getResourceIdentifierOrThrow(ResourceType.LAYOUT, name);
}
}

View File

@@ -1,7 +1,6 @@
package app.revanced.extension.shared.settings.search;
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.ArgbEvaluator;
@@ -33,6 +32,7 @@ import java.lang.reflect.Method;
import java.util.List;
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.ColorPickerPreference;
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 ID_PREFERENCE_TITLE = getResourceIdentifierOrThrow(
"preference_title", "id");
ResourceType.ID, "preference_title");
protected static final int ID_PREFERENCE_SUMMARY = getResourceIdentifierOrThrow(
"preference_summary", "id");
ResourceType.ID, "preference_summary");
protected static final int ID_PREFERENCE_PATH = getResourceIdentifierOrThrow(
"preference_path", "id");
ResourceType.ID, "preference_path");
protected static final int ID_PREFERENCE_SWITCH = getResourceIdentifierOrThrow(
"preference_switch", "id");
ResourceType.ID, "preference_switch");
protected static final int ID_PREFERENCE_COLOR_DOT = getResourceIdentifierOrThrow(
"preference_color_dot", "id");
ResourceType.ID, "preference_color_dot");
protected static class RegularViewHolder {
TextView titleView;
@@ -275,7 +275,7 @@ public abstract class BaseSearchResultsAdapter extends ArrayAdapter<BaseSearchRe
holder.titleView.setText(item.highlightedTitle);
holder.summaryView.setText(item.highlightedSummary);
holder.summaryView.setVisibility(TextUtils.isEmpty(item.highlightedSummary) ? View.GONE : View.VISIBLE);
holder.iconView.setImageResource(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON);
holder.iconView.setImageResource(BaseSearchViewController.getSearchIcon());
}
/**

View File

@@ -14,6 +14,7 @@ import android.preference.PreferenceGroup;
import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
@@ -37,6 +38,7 @@ import java.util.Set;
import java.util.regex.Pattern;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
@@ -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 ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow("revanced_search_view", "id");
protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow("revanced_search_view_container", "id");
protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow("action_search", "id");
protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow("revanced_settings_fragments", "id");
public static final int DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON =
getResourceIdentifierOrThrow("revanced_settings_search_icon", "drawable");
protected static final int MENU_REVANCED_SEARCH_MENU =
getResourceIdentifierOrThrow("revanced_search_menu", "menu");
protected static final int ID_REVANCED_SEARCH_VIEW = getResourceIdentifierOrThrow(
ResourceType.ID, "revanced_search_view");
protected static final int ID_REVANCED_SEARCH_VIEW_CONTAINER = getResourceIdentifierOrThrow(
ResourceType.ID, "revanced_search_view_container");
protected static final int ID_ACTION_SEARCH = getResourceIdentifierOrThrow(
ResourceType.ID, "action_search");
protected static final int ID_REVANCED_SETTINGS_FRAGMENTS = getResourceIdentifierOrThrow(
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.
@@ -112,7 +129,7 @@ public abstract class BaseSearchViewController {
// Retrieve SearchView and container from XML.
searchView = activity.findViewById(ID_REVANCED_SEARCH_VIEW);
EditText searchEditText = searchView.findViewById(Utils.getResourceIdentifierOrThrow(
"android:id/search_src_text", null));
null, "android:id/search_src_text"));
// Disable fullscreen keyboard mode.
searchEditText.setImeOptions(searchEditText.getImeOptions() | EditorInfo.IME_FLAG_NO_EXTRACT_UI);
@@ -248,6 +265,10 @@ public abstract class BaseSearchViewController {
}
return false;
});
// Set bold icon if needed.
MenuItem search = toolbar.getMenu().findItem(ID_ACTION_SEARCH);
search.setIcon(getSearchIcon());
}
/**
@@ -524,7 +545,7 @@ public abstract class BaseSearchViewController {
noResultsPreference.setTitle(str("revanced_settings_search_no_results_title", query));
noResultsPreference.setSummary(str("revanced_settings_search_no_results_summary"));
noResultsPreference.setSelectable(false);
noResultsPreference.setIcon(DRAWABLE_REVANCED_SETTINGS_SEARCH_ICON);
noResultsPreference.setIcon(getSearchIcon());
filteredSearchItems.add(new BaseSearchResultItem.PreferenceSearchItem(noResultsPreference, "", Collections.emptyList()));
}

View File

@@ -24,6 +24,8 @@ import java.util.Deque;
import java.util.LinkedList;
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.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 ID_CLEAR_HISTORY_BUTTON = getResourceIdentifierOrThrow(
"clear_history_button", "id");
ResourceType.ID, "clear_history_button");
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(
"delete_icon", "id");
ResourceType.ID, "delete_icon");
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(
"empty_history_summary", "id");
ResourceType.ID, "empty_history_summary");
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(
"revanced_settings_search_tips_summary", "id");
ResourceType.ID, "revanced_settings_search_tips_summary");
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(
"revanced_preference_search_history_item", "layout");
ResourceType.LAYOUT, "revanced_preference_search_history_item");
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 Activity activity;
@@ -97,7 +109,8 @@ public class SearchHistoryManager {
// Inflate search history layout.
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(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
@@ -320,17 +333,29 @@ public class SearchHistoryManager {
public void notifyDataSetChanged() {
container.removeAllViews();
for (String query : history) {
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);
View view = inflater.inflate(LAYOUT_REVANCED_PREFERENCE_SEARCH_HISTORY_ITEM,
container, false);
// Set click listener for main item (select 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.
ImageView deleteIcon = view.findViewById(ID_DELETE_ICON);
deleteIcon.setImageResource(Utils.appIsUsingBoldIcons()
? ID_SEARCH_REMOVE_ICON_BOLD
: ID_SEARCH_REMOVE_ICON
);
deleteIcon.setOnClickListener(v -> createAndShowDialog(
query,
str("revanced_settings_search_remove_message"),

View File

@@ -16,7 +16,6 @@ import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -83,22 +82,15 @@ public class StreamingDataRequest {
*/
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(
new LinkedHashMap<>(100) {
/**
* 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.
}
});
Utils.createSizeRestrictedMap(50));
/**
* Strings found in the response if the video is a livestream.

View File

@@ -1,6 +1,7 @@
package app.revanced.extension.spotify.layout.hide.createbutton;
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.ResourceIdComponentFilter;
import app.revanced.extension.spotify.shared.ComponentFilters.StringComponentFilter;
@@ -16,7 +17,7 @@ public final class HideCreateButtonPatch {
* 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(
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,
// 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.
@@ -28,7 +29,7 @@ public final class HideCreateButtonPatch {
* Used in older versions of the app.
*/
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.

View File

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

View File

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

View File

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

View File

@@ -1,216 +0,0 @@
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 com.strava.core.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(name, "string");
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

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

View File

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

View File

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

View File

@@ -1,44 +0,0 @@
package com.strava.core.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

@@ -1,16 +0,0 @@
package com.strava.core.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

@@ -1,17 +0,0 @@
package com.strava.core.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

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

View File

@@ -1,286 +0,0 @@
package com.strava.photos.data;
import com.strava.core.data.MediaDimension;
import com.strava.core.data.MediaType;
import com.strava.core.data.RemoteMediaContent;
import com.strava.core.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

@@ -1,14 +1,18 @@
package app.revanced.extension.twitch;
import app.revanced.extension.shared.ResourceType;
public class Utils {
/* Called from SettingsPatch smali */
public static int getStringId(String name) {
return app.revanced.extension.shared.Utils.getResourceIdentifier(name, "string");
return app.revanced.extension.shared.Utils.getResourceIdentifier(
ResourceType.STRING, name);
}
/* Called from SettingsPatch smali */
public static int getDrawableId(String name) {
return app.revanced.extension.shared.Utils.getResourceIdentifier(name, "drawable");
return app.revanced.extension.shared.Utils.getResourceIdentifier(
ResourceType.DRAWABLE, name);
}
}

View File

@@ -4,19 +4,21 @@ import static app.revanced.extension.twitch.Utils.getStringId;
import android.content.Intent;
import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.twitch.settings.preference.TwitchPreferenceFragment;
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
import tv.twitch.android.settings.SettingsActivity;
import java.util.ArrayList;
import java.util.List;
/**
* Hooks AppCompatActivity to inject a custom {@link TwitchPreferenceFragment}.
*/
@@ -108,7 +110,7 @@ public class TwitchActivityHook {
base.getFragmentManager()
.beginTransaction()
.replace(Utils.getResourceIdentifier("fragment_container", "id"), fragment)
.replace(Utils.getResourceIdentifier(ResourceType.ID, "fragment_container"), fragment)
.commit();
return true;
}

View File

@@ -528,14 +528,8 @@ public final class AlternativeThumbnailsPatch {
* Cache used to verify if an alternative thumbnails exists for a given video id.
*/
@GuardedBy("itself")
private static final Map<String, VerifiedQualities> altVideoIdLookup = new LinkedHashMap<>(100) {
private static final int CACHE_LIMIT = 1000;
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
}
};
private static final Map<String, VerifiedQualities> altVideoIdLookup =
Utils.createSizeRestrictedMap(1000);
private static VerifiedQualities getVerifiedQualities(@NonNull String videoId, boolean returnNullIfDoesNotExist) {
synchronized (altVideoIdLookup) {

View File

@@ -7,6 +7,7 @@ import androidx.annotation.Nullable;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
@@ -50,7 +51,7 @@ public class ChangeHeaderPatch {
return null;
}
final int identifier = Utils.getResourceIdentifier(attributeName, "attr");
final int identifier = Utils.getResourceIdentifier(ResourceType.ATTR, attributeName);
if (identifier == 0) {
// Should never happen.
Logger.printException(() -> "Could not find attribute: " + drawableName);
@@ -71,7 +72,7 @@ public class ChangeHeaderPatch {
? "_dark"
: "_light");
final int identifier = Utils.getResourceIdentifier(drawableFullName, "drawable");
final int identifier = Utils.getResourceIdentifier(ResourceType.DRAWABLE, drawableFullName);
if (identifier != 0) {
return Utils.getContext().getDrawable(identifier);
}

View File

@@ -21,7 +21,7 @@ public final class DownloadsPatch {
/**
* Injection point.
*/
public static void activityCreated(Activity mainActivity) {
public static void setMainActivity(Activity mainActivity) {
activityRef = new WeakReference<>(mainActivity);
}

View File

@@ -0,0 +1,24 @@
package app.revanced.extension.youtube.patches;
import java.util.Map;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class FixContentProviderPatch {
/**
* Injection point.
*/
public static void removeNullMapEntries(Map<?, ?> map) {
map.entrySet().removeIf(entry -> {
Object value = entry.getValue();
if (value == null) {
Logger.printDebug(() -> "Removing content provider key with null value: " + entry.getKey());
return true;
}
return false;
});
}
}

View File

@@ -7,6 +7,7 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
@@ -29,6 +30,15 @@ public final class HidePlayerOverlayButtonsPatch {
return Settings.HIDE_CAST_BUTTON.get() ? View.GONE : original;
}
/**
* Injection point.
*/
public static boolean getCastButtonOverrideV2(boolean original) {
if (Settings.HIDE_CAST_BUTTON.get()) return false;
return original;
}
/**
* Injection point.
*/
@@ -40,10 +50,10 @@ public final class HidePlayerOverlayButtonsPatch {
= Settings.HIDE_PLAYER_PREVIOUS_NEXT_BUTTONS.get();
private static final int PLAYER_CONTROL_PREVIOUS_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
"player_control_previous_button_touch_area", "id");
ResourceType.ID, "player_control_previous_button_touch_area");
private static final int PLAYER_CONTROL_NEXT_BUTTON_TOUCH_AREA_ID = getResourceIdentifierOrThrow(
"player_control_next_button_touch_area", "id");
ResourceType.ID, "player_control_next_button_touch_area");
/**
* Injection point.

View File

@@ -4,7 +4,17 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class HideSeekbarPatch {
/**
* Injection point.
*/
public static boolean hideSeekbar() {
return Settings.HIDE_SEEKBAR.get();
}
/**
* Injection point.
*/
public static boolean useFullscreenLargeSeekbar(boolean original) {
return Settings.FULLSCREEN_LARGE_SEEKBAR.get();
}
}

View File

@@ -15,6 +15,7 @@ import androidx.annotation.Nullable;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.youtube.settings.Settings;
@@ -115,7 +116,7 @@ public final class MiniplayerPatch {
* Resource is not present in older targets, and this field will be zero.
*/
private static final int MODERN_OVERLAY_SUBTITLE_TEXT
= Utils.getResourceIdentifier("modern_miniplayer_subtitle_text", "id");
= Utils.getResourceIdentifier(ResourceType.ID, "modern_miniplayer_subtitle_text");
private static final MiniplayerType CURRENT_TYPE = Settings.MINIPLAYER_TYPE.get();
@@ -378,6 +379,19 @@ public final class MiniplayerPatch {
return original;
}
/**
* Injection point.
*/
public static boolean allowBoldIcons(boolean original) {
if (CURRENT_TYPE == MINIMAL) {
// Minimal player does not have the correct pause/play icon (it's too large).
// Use the non bold icons instead.
return false;
}
return original;
}
/**
* Injection point.
*/

View File

@@ -5,12 +5,11 @@ import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButt
import android.os.Build;
import android.view.View;
import android.widget.TextView;
import java.util.EnumMap;
import java.util.Map;
import android.widget.TextView;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
@@ -30,13 +29,13 @@ public final class NavigationButtonsPatch {
private static final boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
private static final Boolean DISABLE_TRANSLUCENT_STATUS_BAR
private static final boolean DISABLE_TRANSLUCENT_STATUS_BAR
= Settings.DISABLE_TRANSLUCENT_STATUS_BAR.get();
private static final Boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT
private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT.get();
private static final Boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
private static final boolean DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK
= Settings.DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK.get();
/**
@@ -62,6 +61,13 @@ public final class NavigationButtonsPatch {
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
}
/**
* Injection point.
*/
public static boolean useAnimatedNavigationButtons(boolean original) {
return Settings.NAVIGATION_BAR_ANIMATIONS.get();
}
/**
* Injection point.
*/

View File

@@ -20,15 +20,6 @@ public class OpenShortsInRegularPlayerPatch {
REGULAR_PLAYER_FULLSCREEN
}
static {
if (!VersionCheckPatch.IS_19_46_OR_GREATER
&& Settings.SHORTS_PLAYER_TYPE.get() == ShortsPlayerType.REGULAR_PLAYER_FULLSCREEN) {
// User imported newer settings to an older app target.
Logger.printInfo(() -> "Resetting " + Settings.SHORTS_PLAYER_TYPE);
Settings.SHORTS_PLAYER_TYPE.resetToDefault();
}
}
private static WeakReference<Activity> mainActivityRef = new WeakReference<>(null);
private static volatile boolean overrideBackPressToExit;

View File

@@ -24,18 +24,20 @@ public class OpenVideosFullscreenHookPatch {
/**
* Injection point.
*
* Returns negated value.
*/
public static boolean openVideoFullscreenPortrait(boolean original) {
public static boolean doNotOpenVideoFullscreenPortrait(boolean original) {
Boolean openFullscreen = openNextVideoFullscreen;
if (openFullscreen != null) {
openNextVideoFullscreen = null;
return openFullscreen;
return !openFullscreen;
}
if (!isFullScreenPatchIncluded()) {
return original;
}
return Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get();
return !Settings.OPEN_VIDEOS_FULLSCREEN_PORTRAIT.get();
}
}

View File

@@ -1,30 +0,0 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class PauseOnAudioInterruptPatch {
private static final int AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK = -3;
private static final int AUDIOFOCUS_LOSS_TRANSIENT = -2;
/**
* Injection point for AudioFocusRequest builder.
* Returns true if audio ducking should be disabled (willPauseWhenDucked = true).
*/
public static boolean shouldPauseOnAudioInterrupt() {
return Settings.PAUSE_ON_AUDIO_INTERRUPT.get();
}
/**
* Injection point for onAudioFocusChange callback.
* Converts AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK to AUDIOFOCUS_LOSS_TRANSIENT
* when the setting is enabled, causing YouTube to pause instead of ducking.
*/
public static int overrideAudioFocusChange(int focusChange) {
if (Settings.PAUSE_ON_AUDIO_INTERRUPT.get() && focusChange == AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
return AUDIOFOCUS_LOSS_TRANSIENT;
}
return focusChange;
}
}

View File

@@ -42,7 +42,7 @@ public class PlayerControlsPatch {
Logger.printDebug(() -> "fullscreen button visibility: "
+ (visibility == View.VISIBLE ? "VISIBLE" :
visibility == View.GONE ? "GONE" : "INVISIBLE"));
visibility == View.GONE ? "GONE" : "INVISIBLE"));
fullscreenButtonVisibilityChanged(visibility == View.VISIBLE);
}

View File

@@ -1,19 +1,29 @@
package app.revanced.extension.youtube.patches;
import android.app.AlertDialog;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
/** @noinspection unused*/
@SuppressWarnings("unused")
public class RemoveViewerDiscretionDialogPatch {
/**
* Injection point.
*/
public static void confirmDialog(AlertDialog dialog) {
if (!Settings.REMOVE_VIEWER_DISCRETION_DIALOG.get()) {
// Since the patch replaces the AlertDialog#show() method, we need to call the original method here.
dialog.show();
if (Settings.REMOVE_VIEWER_DISCRETION_DIALOG.get()) {
Logger.printDebug(() -> "Clicking alert dialog dismiss button");
final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setSoundEffectsEnabled(false);
button.performClick();
return;
}
final var button = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
button.setSoundEffectsEnabled(false);
button.performClick();
// Since the patch replaces the AlertDialog#show() method, we need to call the original method here.
Logger.printDebug(() -> "Showing alert dialog");
dialog.show();
}
}

View File

@@ -13,9 +13,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Objects;
import java.util.Set;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.patches.components.ReturnYouTubeDislikeFilter;
import app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeDislike;
import app.revanced.extension.youtube.settings.Settings;
@@ -131,6 +133,10 @@ public class ReturnYouTubeDislikePatch {
String conversionContextString = conversionContext.toString();
if (Settings.RYD_ENABLED.get()) { // FIXME: Remove this.
Logger.printDebug(() -> "RYD conversion context: " + conversionContext);
}
if (isRollingNumber && !conversionContextString.contains("video_action_bar.e")) {
return original;
}

View File

@@ -2,8 +2,6 @@ package app.revanced.extension.youtube.patches;
import android.app.Activity;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -78,7 +76,7 @@ public class ShortsAutoplayPatch {
/**
* Injection point.
*/
public static Enum<?> changeShortsRepeatBehavior(@Nullable Enum<?> original) {
public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) {
try {
final boolean autoplay;
@@ -95,19 +93,19 @@ public class ShortsAutoplayPatch {
autoplay = Settings.SHORTS_AUTOPLAY.get();
}
final ShortsLoopBehavior behavior = autoplay
Enum<?> overrideBehavior = (autoplay
? ShortsLoopBehavior.SINGLE_PLAY
: ShortsLoopBehavior.REPEAT;
: ShortsLoopBehavior.REPEAT).ytEnumValue;
if (behavior.ytEnumValue != null) {
if (overrideBehavior != null) {
Logger.printDebug(() -> {
String name = (original == null ? "unknown (null)" : original.name());
return behavior == original
return overrideBehavior == original
? "Behavior setting is same as original. Using original: " + name
: "Changing Shorts repeat behavior from: " + name + " to: " + behavior.name();
: "Changing Shorts repeat behavior from: " + name + " to: " + overrideBehavior.name();
});
return behavior.ytEnumValue;
return overrideBehavior;
}
if (original == null) {
@@ -118,13 +116,12 @@ public class ShortsAutoplayPatch {
return unknown;
}
} catch (Exception ex) {
Logger.printException(() -> "changeShortsRepeatBehavior failure", ex);
Logger.printException(() -> "changeShortsRepeatState failure", ex);
}
return original;
}
/**
* Injection point.
*/

View File

@@ -19,5 +19,12 @@ public class VersionCheckPatch {
public static final boolean IS_19_29_OR_GREATER = isVersionOrGreater("19.29.00");
@Deprecated
public static final boolean IS_19_34_OR_GREATER = isVersionOrGreater("19.34.00");
public static final boolean IS_19_46_OR_GREATER = isVersionOrGreater("19.46.00");
public static final boolean IS_20_21_OR_GREATER = isVersionOrGreater("20.21.00");
public static final boolean IS_20_22_OR_GREATER = isVersionOrGreater("20.22.00");
public static final boolean IS_20_31_OR_GREATER = isVersionOrGreater("20.31.00");
public static final boolean IS_20_37_OR_GREATER = isVersionOrGreater("20.37.00");
}

View File

@@ -11,9 +11,6 @@ import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
@@ -156,8 +153,8 @@ public final class AdsFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == playerShoppingShelf) {
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
}

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch;
import app.revanced.extension.youtube.settings.Settings;
@@ -21,7 +19,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
isVideoQualityMenuVisible = true;

View File

@@ -1,13 +1,10 @@
package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class ButtonsFilter extends Filter {
final class ButtonsFilter extends Filter {
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.e";
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.e";
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.e";
@@ -42,7 +39,6 @@ public final class ButtonsFilter extends Filter {
addPathCallbacks(
likeSubscribeGlow,
bufferFilterPathGroup,
new StringFilterGroup(
Settings.HIDE_LIKE_DISLIKE_BUTTON,
"|segmented_like_dislike_button"
@@ -61,6 +57,12 @@ public final class ButtonsFilter extends Filter {
)
);
// FIXME: 20.22+ filtering of the action buttons doesn't work because
// the buffer is the same for all buttons.
if (!VersionCheckPatch.IS_20_22_OR_GREATER) {
addPathCallbacks(bufferFilterPathGroup);
}
bufferButtonsGroupList.addAll(
new ByteArrayFilterGroup(
Settings.HIDE_REPORT_BUTTON,
@@ -112,17 +114,19 @@ public final class ButtonsFilter extends Filter {
}
private boolean isEveryFilterGroupEnabled() {
for (var group : pathCallbacks)
for (var group : pathCallbacks) {
if (!group.isEnabled()) return false;
}
for (var group : bufferButtonsGroupList)
for (var group : bufferButtonsGroupList) {
if (!group.isEnabled()) return false;
}
return true;
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == likeSubscribeGlow) {
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))

View File

@@ -1,12 +1,10 @@
package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public final class CommentsFilter extends Filter {
final class CommentsFilter extends Filter {
private static final String COMMENT_COMPOSER_PATH = "comment_composer.e";
@@ -90,8 +88,8 @@ public final class CommentsFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == chipBar) {
// Playlist sort button uses same components and must only filter if the player is opened.
return PlayerType.getCurrent().isMaximizedOrFullscreen()

View File

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

View File

@@ -1,14 +1,11 @@
package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public final class DescriptionComponentsFilter extends Filter {
final class DescriptionComponentsFilter extends Filter {
private static final String INFOCARDS_SECTION_PATH = "infocards_section.e";
@@ -131,8 +128,8 @@ public final class DescriptionComponentsFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) {
// Only hide if player is open, in case this component is used somewhere else.

View File

@@ -1,12 +1,9 @@
package app.revanced.extension.shared.patches.litho;
package app.revanced.extension.youtube.patches.components;
import java.util.ArrayList;
import java.util.Arrays;
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.
*
@@ -17,11 +14,11 @@ import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGr
* either an identifier or a path.
* 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)
* or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern).
* or a {@link ByteArrayFilterGroupList} (if searching for more than 1 pattern).
*
* All callbacks must be registered before the constructor completes.
*/
public abstract class Filter {
abstract class Filter {
public enum FilterContentType {
IDENTIFIER,
@@ -68,7 +65,7 @@ public abstract class Filter {
* @param contentIndex Matched index of the identifier or path.
* @return True if the litho component should be filtered out.
*/
public boolean isFiltered(String identifier, String path, byte[] buffer,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
return true;
}

View File

@@ -0,0 +1,214 @@
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.NonNull;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.ByteTrieSearch;
abstract class FilterGroup<T> {
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;
protected 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);
}
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 ByteArrayFilterGroupList#check(byte[])},
* which uses a prefix tree to give better performance.
*/
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,22 +1,21 @@
package app.revanced.extension.shared.patches.litho;
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.NonNull;
import java.util.*;
import java.util.function.Consumer;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
private final List<T> filterGroups = new ArrayList<>();
private final TrieSearch<V> search = createSearchGraph();
@SafeVarargs
public final void addAll(final T... groups) {
protected final void addAll(final T... groups) {
filterGroups.addAll(Arrays.asList(groups));
for (T group : groups) {
@@ -42,7 +41,18 @@ public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements It
return filterGroups.iterator();
}
public FilterGroup.FilterGroupResult check(V stack) {
@Override
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();
search.matches(stack, result);
return result;
@@ -50,21 +60,21 @@ public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements It
}
protected abstract TrieSearch<V> createSearchGraph();
}
public static final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
protected StringTrieSearch createSearchGraph() {
return new StringTrieSearch();
}
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
protected StringTrieSearch createSearchGraph() {
return new StringTrieSearch();
}
}
/**
* 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
* than a prefix tree to search for only 1 pattern.
*/
public static final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
protected ByteTrieSearch createSearchGraph() {
return new ByteTrieSearch();
}
/**
* 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
* than a prefix tree to search for only 1 pattern.
*/
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
protected ByteTrieSearch createSearchGraph() {
return new ByteTrieSearch();
}
}

View File

@@ -1,8 +1,6 @@
package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
@SuppressWarnings("unused")
public final class HideInfoCardsFilter extends Filter {

View File

@@ -17,8 +17,6 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType;
@@ -43,7 +41,7 @@ import app.revanced.extension.youtube.shared.PlayerType;
* - When using whole word syntax, some keywords may need additional pluralized variations.
*/
@SuppressWarnings("unused")
public final class KeywordContentFilter extends Filter {
final class KeywordContentFilter extends Filter {
/**
* Strings found in the buffer for every videos. Full strings should be specified.
@@ -556,8 +554,8 @@ public final class KeywordContentFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
return false;
}

View File

@@ -1,5 +1,6 @@
package app.revanced.extension.youtube.patches.components;
import static app.revanced.extension.youtube.patches.VersionCheckPatch.IS_20_21_OR_GREATER;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import android.graphics.drawable.Drawable;
@@ -14,9 +15,6 @@ import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
import app.revanced.extension.youtube.patches.ChangeHeaderPatch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar;
@@ -345,7 +343,7 @@ public final class LayoutComponentsFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// This identifier is used not only in players but also in search results:
// https://github.com/ReVanced/revanced-patches/issues/3245
@@ -402,20 +400,22 @@ public final class LayoutComponentsFilter extends Filter {
* Injection point.
* Called from a different place then the other filters.
*/
public static boolean filterMixPlaylists(Object conversionContext, @Nullable final byte[] bytes) {
public static boolean filterMixPlaylists(Object conversionContext, @Nullable byte[] buffer) {
// Edit: This hook may no longer be needed, and mix playlist filtering
// might be possible using the existing litho filters.
try {
if (!Settings.HIDE_MIX_PLAYLISTS.get()) {
return false;
}
if (bytes == null) {
Logger.printDebug(() -> "bytes is null");
if (buffer == null) {
Logger.printDebug(() -> "buffer is null");
return false;
}
if (mixPlaylists.check(bytes).isFiltered()
if (mixPlaylists.check(buffer).isFiltered()
// Prevent hiding the description of some videos accidentally.
&& !mixPlaylistsBufferExceptions.check(bytes).isFiltered()
&& !mixPlaylistsBufferExceptions.check(buffer).isFiltered()
// Prevent playlist items being hidden, if a mix playlist is present in it.
// Check last since it requires creating a context string.
//
@@ -478,11 +478,23 @@ public final class LayoutComponentsFilter extends Filter {
: height;
}
private static final boolean HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS_ENABLED
= Settings.HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS.get();
/**
* Injection point.
*/
public static void hideInRelatedVideos(View chipView) {
Utils.hideViewBy0dpUnderCondition(Settings.HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS, chipView);
// Cannot use 0dp hide with later targets, otherwise the suggested videos
// can be shown in full screen mode.
// This behavior may also be present in earlier app targets.
if (IS_20_21_OR_GREATER) {
// FIXME: The filter bar is still briefly shown when dragging the suggested videos
// below the video player.
Utils.hideViewUnderCondition(HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS_ENABLED, chipView);
} else {
Utils.hideViewBy0dpUnderCondition(HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS_ENABLED, chipView);
}
}
private static final boolean HIDE_DOODLES_ENABLED = Settings.HIDE_DOODLES.get();
@@ -507,7 +519,9 @@ public final class LayoutComponentsFilter extends Filter {
&& NavigationBar.isSearchBarActive()
// Search bar can be active but behind the player.
&& !PlayerType.getCurrent().isMaximizedOrFullscreen()) {
Utils.hideViewByLayoutParams(view);
// FIXME: "Show more" button is visible hidden,
// but an empty space remains that can be clicked.
Utils.hideViewBy0dp(view);
}
}

View File

@@ -1,17 +1,20 @@
package app.revanced.extension.shared.patches.litho;
package app.revanced.extension.youtube.patches.components;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup;
import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class LithoFilterPatch {
@@ -38,7 +41,7 @@ public final class LithoFilterPatch {
builder.append(identifier);
builder.append(" Path: ");
builder.append(path);
if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
if (Settings.DEBUG_PROTOBUFFER.get()) {
builder.append(" BufferStrings: ");
findAsciiStrings(builder, buffer);
}
@@ -75,6 +78,15 @@ public final class LithoFilterPatch {
}
}
/**
* Placeholder for actual filters.
*/
private static final class DummyFilter extends Filter { }
private static final Filter[] filters = new Filter[] {
new DummyFilter() // Replaced during patching, do not touch.
};
/**
* Litho layout fixed thread pool size override.
* <p>
@@ -92,25 +104,50 @@ public final class LithoFilterPatch {
private static final int LITHO_LAYOUT_THREAD_POOL_SIZE = 1;
/**
* Placeholder for actual filters.
* 20.22+ cannot use the thread buffer, because frequently the buffer is not correct,
* especially for components that are recreated such as dragging off screen then back on screen.
* Instead, parse the identifier found near the start of the buffer and use that to
* identify the correct buffer to use when filtering.
*/
private static final class DummyFilter extends Filter { }
private static final boolean EXTRACT_IDENTIFIER_FROM_BUFFER = VersionCheckPatch.IS_20_22_OR_GREATER;
private static final Filter[] filters = new Filter[] {
new DummyFilter() // Replaced patching, do not touch.
};
/**
* Turns on additional logging, used for development purposes only.
*/
public static final boolean DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER = false;
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
/**
* String suffix for components.
* Can be any of: ".eml", ".e-b", ".eml-js", "e-js-b"
*/
private static final String LITHO_COMPONENT_EXTENSION = ".e";
private static final byte[] LITHO_COMPONENT_EXTENSION_BYTES = LITHO_COMPONENT_EXTENSION.getBytes(StandardCharsets.US_ASCII);
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.
* Used for 20.21 and lower.
*/
private static final ThreadLocal<byte[]> bufferThreadLocal = new ThreadLocal<>();
/**
* Identifier to protocol buffer mapping. Only used for 20.22+.
* Thread local is needed because filtering is multi-threaded and each thread can load
* a different component with the same identifier.
*/
private static final ThreadLocal<Map<String, byte[]>> identifierToBufferThread = new ThreadLocal<>();
/**
* Global shared buffer. Used only if the buffer is not found in the ThreadLocal.
*/
private static final Map<String, byte[]> identifierToBufferGlobal
= Collections.synchronizedMap(createIdentifierToBufferMap());
private static final StringTrieSearch pathSearchTree = new StringTrieSearch();
private static final StringTrieSearch identifierSearchTree = new StringTrieSearch();
static {
for (Filter filter : filters) {
filterUsingCallbacks(identifierSearchTree, filter,
@@ -162,16 +199,107 @@ public final class LithoFilterPatch {
}
}
private static Map<String, byte[]> createIdentifierToBufferMap() {
// It's unclear how many items should be cached. This is a guess.
return Utils.createSizeRestrictedMap(100);
}
/**
* Helper function that differs from {@link Character#isDigit(char)}
* as this only matches ascii and not unicode numbers.
*/
private static boolean isAsciiNumber(byte character) {
return '0' <= character && character <= '9';
}
private static boolean isAsciiLowerCaseLetter(byte character) {
return 'a' <= character && character <= 'z';
}
/**
* 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);
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
StringBuilder builder = new StringBuilder();
LithoFilterParameters.findAsciiStrings(builder, buffer);
Logger.printDebug(() -> "New buffer: " + builder);
}
// Could use Boyer-Moore-Horspool since the string is ASCII and has a limited number of
// unique characters, but it seems to be slower since the extra overhead of checking the
// bad character array negates any performance gain of skipping a few extra subsearches.
int emlIndex = -1;
final int emlStringLength = LITHO_COMPONENT_EXTENSION_BYTES.length;
for (int i = 0, lastStartIndex = buffer.length - emlStringLength; i <= lastStartIndex; i++) {
boolean match = true;
for (int j = 0; j < emlStringLength; j++) {
if (buffer[i + j] != LITHO_COMPONENT_EXTENSION_BYTES[j]) {
match = false;
break;
}
}
if (match) {
emlIndex = i;
break;
}
}
if (emlIndex < 0) {
// Buffer is not used for creating a new litho component.
return;
}
int startIndex = emlIndex - 1;
while (startIndex > 0) {
final byte character = buffer[startIndex];
int startIndexFinal = startIndex;
if (isAsciiLowerCaseLetter(character) || isAsciiNumber(character) || character == '_') {
// Valid character for the first path element.
startIndex--;
} else {
startIndex++;
break;
}
}
// Strip away any numbers on the start of the identifier, which can
// be from random data in the buffer before the identifier starts.
while (true) {
final byte character = buffer[startIndex];
if (isAsciiNumber(character)) {
startIndex++;
} else {
break;
}
}
// Find the pipe character after the identifier.
int endIndex = -1;
for (int i = emlIndex, length = buffer.length; i < length; i++) {
if (buffer[i] == '|') {
endIndex = i;
break;
}
}
if (endIndex < 0) {
Logger.printException(() -> "Could not find buffer identifier");
return;
}
String identifier = new String(buffer, startIndex, endIndex - startIndex, StandardCharsets.US_ASCII);
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER) {
Logger.printDebug(() -> "Found buffer for identifier: " + identifier);
}
identifierToBufferGlobal.put(identifier, buffer);
Map<String, byte[]> map = identifierToBufferThread.get();
if (map == null) {
map = createIdentifierToBufferMap();
identifierToBufferThread.set(map);
}
map.put(identifier, buffer);
}
/**
@@ -179,46 +307,70 @@ public final class LithoFilterPatch {
* 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());
// 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.array());
}
}
/**
* Injection point.
*/
public static boolean isFiltered(String lithoIdentifier, StringBuilder pathBuilder) {
public static boolean isFiltered(String identifier, StringBuilder pathBuilder) {
try {
if (lithoIdentifier.isEmpty() && pathBuilder.length() == 0) {
if (identifier.isEmpty() || pathBuilder.length() == 0) {
return false;
}
byte[] buffer = bufferThreadLocal.get();
byte[] buffer = null;
if (EXTRACT_IDENTIFIER_FROM_BUFFER) {
final int pipeIndex = identifier.indexOf('|');
if (pipeIndex >= 0) {
// If the identifier contains no pipe, then it's not an ".eml" identifier
// and the buffer is not uniquely identified. Typically this only happens
// for subcomponents where buffer filtering is not used.
String identifierKey = identifier.substring(0, pipeIndex);
var map = identifierToBufferThread.get();
if (map != null) {
buffer = map.get(identifierKey);
}
if (buffer == null) {
// Buffer for thread local not found. Use the last buffer found from any thread.
buffer = identifierToBufferGlobal.get(identifierKey);
if (DEBUG_EXTRACT_IDENTIFIER_FROM_BUFFER && buffer == null) {
// No buffer is found for some components, such as
// shorts_lockup_cell.eml on channel profiles.
// For now, just ignore this and filter without a buffer.
Logger.printException(() -> "Could not find global buffer for identifier: " + identifier);
}
}
}
} else {
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.
// Use an empty buffer so the litho id/path filters that do not use a buffer still work.
if (buffer == null) {
buffer = EMPTY_BYTE_ARRAY;
}
LithoFilterParameters parameter = new LithoFilterParameters(
lithoIdentifier, pathBuilder.toString(), buffer);
String path = pathBuilder.toString();
LithoFilterParameters parameter = new LithoFilterParameters(identifier, path, buffer);
Logger.printDebug(() -> "Searching " + parameter);
if (identifierSearchTree.matches(parameter.identifier, parameter)) {
return true;
}
if (pathSearchTree.matches(parameter.path, parameter)) {
return true;
}
return identifierSearchTree.matches(identifier, parameter)
|| pathSearchTree.matches(path, parameter);
} catch (Exception ex) {
Logger.printException(() -> "isFiltered failure", ex);
}

View File

@@ -1,7 +1,5 @@
package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings;
@@ -38,7 +36,7 @@ public final class PlaybackSpeedMenuFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == oldPlaybackMenuGroup) {
isOldPlaybackSpeedMenuVisible = true;

View File

@@ -3,16 +3,13 @@ package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
import java.util.List;
@SuppressWarnings("unused")
public final class PlayerFlyoutMenuItemsFilter extends Filter {
public class PlayerFlyoutMenuItemsFilter extends Filter {
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
@Override
@@ -97,7 +94,7 @@ public final class PlayerFlyoutMenuItemsFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == videoQualityMenuFooter) {
return true;

View File

@@ -4,18 +4,15 @@ import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.ReturnYouTubeDislikePatch;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.TrieSearch;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
/**
* Searches for video id's in the proto buffer of Shorts dislike.
@@ -36,18 +33,7 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
* Cannot use {@link LinkedHashSet} because it's missing #removeEldestEntry().
*/
@GuardedBy("itself")
private static final Map<String, Boolean> lastVideoIds = new LinkedHashMap<>() {
/**
* Number of video id's to keep track of for searching thru the buffer.
* A minimum value of 3 should be sufficient, but check a few more just in case.
*/
private static final int NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK = 5;
@Override
protected boolean removeEldestEntry(Entry eldest) {
return size() > NUMBER_OF_LAST_VIDEO_IDS_TO_TRACK;
}
};
private static final Map<String, Boolean> lastVideoIds = Utils.createSizeRestrictedMap(5);
/**
* Injection point.
@@ -87,13 +73,13 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
return false;
}
FilterGroupResult result = videoIdFilterGroup.check(buffer);
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(buffer);
if (result.isFiltered()) {
String matchedVideoId = findVideoId(buffer);
// Matched video will be null if in incognito mode.

View File

@@ -11,9 +11,7 @@ import java.util.Arrays;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.patches.litho.Filter;
import app.revanced.extension.shared.patches.litho.FilterGroup.*;
import app.revanced.extension.shared.patches.litho.FilterGroupList.*;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType;
@@ -226,7 +224,11 @@ public final class ShortsFilter extends Filter {
videoActionButton = new StringFilterGroup(
null,
// Can be simply 'button.e', 'shorts_video_action_button.e' or 'reel_action_button.e'
// Can be any of:
// button.eml
// shorts_video_action_button.eml
// reel_action_button.eml
// reel_pivot_button.eml
"button.e"
);
@@ -237,31 +239,37 @@ public final class ShortsFilter extends Filter {
addPathCallbacks(
shortsCompactFeedVideo, joinButton, subscribeButton, paidPromotionLabel, autoDubbedLabel,
shortsActionBar, suggestedAction, pausedOverlayButtons, channelBar, previewComment,
suggestedAction, pausedOverlayButtons, channelBar, previewComment,
fullVideoLinkLabel, videoTitle, useSoundButton, reelSoundMetadata, soundButton, infoPanel,
stickers, likeFountain, likeButton, dislikeButton, livePreview
);
//
// All other action buttons.
//
videoActionButtonBuffer.addAll(
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
"reel_comment_button",
"youtube_shorts_comment_outline"
),
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_SHARE_BUTTON,
"reel_share_button",
"youtube_shorts_share_outline"
),
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_REMIX_BUTTON,
"reel_remix_button",
"youtube_shorts_remix_outline"
)
);
// FIXME: The Shorts buffer is very different with 20.22+ and if any of these filters
// are enabled then all Shorts player vertical buttons are hidden.
if (!VersionCheckPatch.IS_20_22_OR_GREATER) {
addPathCallbacks(shortsActionBar);
//
// All other action buttons.
//
videoActionButtonBuffer.addAll(
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
"reel_comment_button",
"youtube_shorts_comment_outline"
),
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_SHARE_BUTTON,
"reel_share_button",
"youtube_shorts_share_outline"
),
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_REMIX_BUTTON,
"reel_remix_button",
"youtube_shorts_remix_outline"
)
);
}
//
// Suggested actions.
@@ -342,7 +350,7 @@ public final class ShortsFilter extends Filter {
}
@Override
public boolean isFiltered(String identifier, String path, byte[] buffer,
boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (contentType == FilterContentType.PATH) {
if (matchedGroup == subscribeButton || matchedGroup == joinButton

View File

@@ -21,7 +21,6 @@ public class RememberVideoQualityPatch {
private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI;
private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE;
public static boolean shouldRememberVideoQuality() {
BooleanSetting preference = ShortsPlayerState.isOpen()
? Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED

View File

@@ -0,0 +1,85 @@
package app.revanced.extension.youtube.patches.theme;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
/**
* Dynamic drawable that is either the regular or bolded ReVanced preference icon.
*
* This is needed because the YouTube ReVanced preference intent is an AndroidX preference,
* and AndroidX classes are not built into Android which makes programmatically changing
* the preference thru patching overly complex. This solves the problem by using a drawable
* wrapper to dynamically pick which icon drawable to use at runtime.
*/
@SuppressWarnings("unused")
public class ReVancedSettingsIconDynamicDrawable extends Drawable {
private final Drawable icon;
public ReVancedSettingsIconDynamicDrawable() {
final int resId = Utils.getResourceIdentifier(ResourceType.DRAWABLE,
Utils.appIsUsingBoldIcons()
? "revanced_settings_icon_bold"
: "revanced_settings_icon"
);
icon = Utils.getContext().getDrawable(resId);
}
@Override
public void draw(@NonNull Canvas canvas) {
icon.draw(canvas);
}
@Override
public void setAlpha(int alpha) {
icon.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
icon.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return icon.getOpacity();
}
@Override
public int getIntrinsicWidth() {
return icon.getIntrinsicWidth();
}
@Override
public int getIntrinsicHeight() {
return icon.getIntrinsicHeight();
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
icon.setBounds(left, top, right, bottom);
}
@Override
public void setBounds(@NonNull Rect bounds) {
super.setBounds(bounds);
icon.setBounds(bounds);
}
@Override
public void onBoundsChange(@NonNull Rect bounds) {
super.onBoundsChange(bounds);
icon.setBounds(bounds);
}
}

View File

@@ -16,6 +16,7 @@ import java.util.Arrays;
import java.util.Scanner;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.settings.Settings;
@@ -101,16 +102,6 @@ public final class SeekbarColorPatch {
return customSeekbarColor;
}
/**
* injection point.
*/
public static boolean useLotteLaunchSplashScreen(boolean original) {
// This method is only used for development purposes to force the old style launch screen.
// Forcing this off on some devices can cause unexplained startup crashes,
// where the lottie animation is still used even though this condition appears to bypass it.
return original; // false = drawable style, true = lottie style.
}
/**
* Injection point.
* Modern Lottie style animation.

View File

@@ -260,7 +260,8 @@ public class ReturnYouTubeDislike {
// middle separator
String middleSeparatorString = compactLayout
? " " + MIDDLE_SEPARATOR_CHARACTER + " "
: " \u2009" + MIDDLE_SEPARATOR_CHARACTER + "\u2009 "; // u2009 = 'narrow space' character
: " \u2009\u2009" + MIDDLE_SEPARATOR_CHARACTER + "\u2009\u2009 "; // u2009 = 'narrow space'
final int shapeInsertionIndex = middleSeparatorString.length() / 2;
Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString);
ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape());
@@ -555,7 +556,8 @@ public class ReturnYouTubeDislike {
if (originalDislikeSpan != null && replacementLikeDislikeSpan != null
&& spansHaveEqualTextAndColor(original, originalDislikeSpan)) {
Logger.printDebug(() -> "Replacing span with previously created dislike span of data: " + videoId);
Logger.printDebug(() -> "Replacing span: " + original + " with " +
"previously created dislike span of data: " + videoId);
return replacementLikeDislikeSpan;
}

View File

@@ -32,7 +32,6 @@ import android.graphics.Color;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.YouTubeAndMusicSettings;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.EnumSetting;
@@ -46,11 +45,11 @@ import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrow
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
import app.revanced.extension.youtube.patches.MiniplayerPatch;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
public class Settings extends YouTubeAndMusicSettings {
public class Settings extends BaseSettings {
// Video
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
@@ -188,7 +187,7 @@ public class Settings extends YouTubeAndMusicSettings {
public static final BooleanSetting MINIPLAYER_DOUBLE_TAP_ACTION = new BooleanSetting("revanced_miniplayer_double_tap_action", TRUE, true, new MiniplayerAnyModernAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_OVERLAY_BUTTONS = new BooleanSetting("revanced_miniplayer_hide_overlay_buttons", FALSE, true, new MiniplayerHideOverlayButtonsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_SUBTEXT = new BooleanSetting("revanced_miniplayer_hide_subtext", FALSE, true, new MiniplayerHideSubtextsAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, new MiniplayerPatch.MiniplayerHideRewindOrOverlayOpacityAvailability());
public static final BooleanSetting MINIPLAYER_HIDE_REWIND_FORWARD = new BooleanSetting("revanced_miniplayer_hide_rewind_forward", TRUE, true, new MiniplayerHideRewindOrOverlayOpacityAvailability());
public static final IntegerSetting MINIPLAYER_WIDTH_DIP = new IntegerSetting("revanced_miniplayer_width_dip", 192, true, new MiniplayerAnyModernAvailability());
public static final IntegerSetting MINIPLAYER_OPACITY = new IntegerSetting("revanced_miniplayer_opacity", 100, true, new MiniplayerHideRewindOrOverlayOpacityAvailability());
@@ -275,6 +274,11 @@ public class Settings extends YouTubeAndMusicSettings {
public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
new ChangeStartPageTypeAvailability());
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION));
// 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));
// Navigation buttons
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true);
@@ -284,6 +288,7 @@ public class Settings extends YouTubeAndMusicSettings {
public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true);
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true,
"revanced_switch_create_with_notifications_button_user_dialog_message");
public static final BooleanSetting NAVIGATION_BAR_ANIMATIONS = new BooleanSetting("revanced_navigation_bar_animations", FALSE);
public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", FALSE, true,
"revanced_disable_translucent_status_bar_user_dialog_message");
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true);
@@ -337,6 +342,7 @@ public class Settings extends YouTubeAndMusicSettings {
public static final BooleanSetting DISABLE_PRECISE_SEEKING_GESTURE = new BooleanSetting("revanced_disable_precise_seeking_gesture", FALSE);
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true);
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE, true);
public static final BooleanSetting FULLSCREEN_LARGE_SEEKBAR = new BooleanSetting("revanced_fullscreen_large_seekbar", FALSE);
public static final BooleanSetting HIDE_TIMESTAMP = new BooleanSetting("revanced_hide_timestamp", FALSE);
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE);
public static final BooleanSetting SEEKBAR_TAPPING = new BooleanSetting("revanced_seekbar_tapping", FALSE);
@@ -352,7 +358,6 @@ public class Settings extends YouTubeAndMusicSettings {
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1, false, false);
public static final BooleanSetting LOOP_VIDEO = new BooleanSetting("revanced_loop_video", FALSE);
public static final BooleanSetting LOOP_VIDEO_BUTTON = new BooleanSetting("revanced_loop_video_button", FALSE);
public static final BooleanSetting PAUSE_ON_AUDIO_INTERRUPT = new BooleanSetting("revanced_pause_on_audio_interrupt", FALSE, true);
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_CHAPTERS = new BooleanSetting("revanced_disable_haptic_feedback_chapters", FALSE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING = new BooleanSetting("revanced_disable_haptic_feedback_precise_seeking", FALSE);
@@ -364,6 +369,8 @@ public class Settings extends YouTubeAndMusicSettings {
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS));
public static final BooleanSetting SPOOF_VIDEO_STREAMS_AV1 = new BooleanSetting("revanced_spoof_video_streams_av1", FALSE, true,
"revanced_spoof_video_streams_av1_user_dialog_message", new SpoofClientAv1Availability());
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
"revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
// Swipe controls
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true);
@@ -376,7 +383,7 @@ public class Settings extends YouTubeAndMusicSettings {
public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_VOLUME_SENSITIVITY = new IntegerSetting("revanced_swipe_volume_sensitivity", 1, true, parent(SWIPE_VOLUME));
public static final EnumSetting<SwipeOverlayStyle> SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL, true,
public static final EnumSetting<SwipeOverlayStyle> SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL,true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 14, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
@@ -405,9 +412,7 @@ public class Settings extends YouTubeAndMusicSettings {
// SponsorBlock
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE);
/**
* Do not use id setting directly. Instead use {@link SponsorBlockSettings}.
*/
/** Do not use id setting directly. Instead use {@link SponsorBlockSettings}. */
public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "", parent(SB_ENABLED));
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
@@ -456,7 +461,7 @@ public class Settings extends YouTubeAndMusicSettings {
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFFFF", false, false);
// Deprecated migrations
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
private static final FloatSetting DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f, false, false);
private static final FloatSetting DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f, false, false);
@@ -472,6 +477,13 @@ public class Settings extends YouTubeAndMusicSettings {
static {
// region Migration
// 20.37+ YT removed parts of the code for the legacy tablet miniplayer.
// This check must remain until the Tablet type is eventually removed.
if (VersionCheckPatch.IS_20_37_OR_GREATER && MINIPLAYER_TYPE.get() == MiniplayerType.TABLET) {
Logger.printInfo(() -> "Resetting miniplayer tablet type");
MINIPLAYER_TYPE.resetToDefault();
}
// Migrate renamed change header enums.
if (HEADER_LOGO.get() == HeaderLogo.REVANCED) {
HEADER_LOGO.save(HeaderLogo.ROUNDED);
@@ -508,12 +520,20 @@ public class Settings extends YouTubeAndMusicSettings {
// or is spoofing to a version the same or newer than this app.
if (!SPOOF_APP_VERSION_TARGET.isSetToDefault() &&
(SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0
|| (Utils.getAppVersionName().compareTo(SPOOF_APP_VERSION_TARGET.get()) <= 0))) {
|| (Utils.getAppVersionName().compareTo(SPOOF_APP_VERSION_TARGET.get()) <= 0))) {
Logger.printInfo(() -> "Resetting spoof app version");
SPOOF_APP_VERSION_TARGET.resetToDefault();
SPOOF_APP_VERSION.resetToDefault();
}
if (!BaseSettings.SETTINGS_DISABLE_BOLD_ICONS.get() && SPOOF_APP_VERSION.get()
&& SPOOF_APP_VERSION_TARGET.get().compareTo("19.35.00") <= 0) {
Logger.printInfo(() -> "Temporarily disabling bold icons that don't work with old spoof targets");
// Don't save and only temporarily overwrite the value so
// if spoofing is turned off the old setting value is used.
BooleanSetting.privateSetValue(BaseSettings.SETTINGS_DISABLE_BOLD_ICONS, false);
}
// VR 1.61 is not selectable in the settings, and it's selected by spoof stream patch if needed.
if (SPOOF_VIDEO_STREAMS_CLIENT_TYPE.get() == ClientType.ANDROID_VR_1_61_48) {
SPOOF_VIDEO_STREAMS_CLIENT_TYPE.resetToDefault();

View File

@@ -7,6 +7,7 @@ import android.preference.PreferenceFragment;
import android.view.View;
import android.widget.Toolbar;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseActivityHook;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
@@ -15,11 +16,28 @@ import app.revanced.extension.youtube.settings.preference.YouTubePreferenceFragm
import app.revanced.extension.youtube.settings.search.YouTubeSearchViewController;
/**
* Hooks LicenseActivity to inject a custom {@link YouTubePreferenceFragment} with a toolbar and search functionality.
* Hooks LicenseActivity to inject a custom {@link YouTubePreferenceFragment}
* with a toolbar and search functionality.
*/
@SuppressWarnings("deprecation")
public class YouTubeActivityHook extends BaseActivityHook {
/**
* How much time has passed since the first launch of the app. Simple check to prevent
* forcing bold icons on first launch where the settings menu is partially broken
* due to missing icon resources the client has not yet received.
*/
private static final long MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS = 30 * 1000; // 30 seconds.
private static final boolean USE_BOLD_ICONS = VersionCheckPatch.IS_20_31_OR_GREATER
&& !Settings.SETTINGS_DISABLE_BOLD_ICONS.get()
&& (System.currentTimeMillis() - Settings.FIRST_TIME_APP_LAUNCHED.get())
> MINIMUM_TIME_AFTER_FIRST_LAUNCH_BEFORE_ALLOWING_BOLD_ICONS;
static {
Utils.setAppIsUsingBoldIcons(USE_BOLD_ICONS);
}
private static int currentThemeValueOrdinal = -1; // Must initially be a non-valid enum ordinal value.
/**
@@ -44,15 +62,7 @@ public class YouTubeActivityHook extends BaseActivityHook {
final var theme = Utils.isDarkModeEnabled()
? "Theme.YouTube.Settings.Dark"
: "Theme.YouTube.Settings";
activity.setTheme(Utils.getResourceIdentifierOrThrow(theme, "style"));
}
/**
* Returns the resource ID for the YouTube settings layout.
*/
@Override
protected int getContentViewResourceId() {
return LAYOUT_REVANCED_SETTINGS_WITH_TOOLBAR;
activity.setTheme(Utils.getResourceIdentifierOrThrow(ResourceType.STYLE, theme));
}
/**
@@ -155,4 +165,12 @@ public class YouTubeActivityHook extends BaseActivityHook {
public static boolean handleBackPress() {
return YouTubeSearchViewController.handleFinish(searchViewController);
}
/**
* Injection point.
*/
@SuppressWarnings("unused")
public static boolean useBoldIcons(boolean original) {
return USE_BOLD_ICONS;
}
}

View File

@@ -19,8 +19,10 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
@@ -72,7 +74,7 @@ public final class NavigationBar {
*/
public static boolean isSearchBarActive() {
View searchbarResults = searchBarResultsRef.get();
return searchbarResults != null && searchbarResults.getParent() != null;
return searchbarResults != null && searchbarResults.isShown();
}
public static boolean isBackButtonVisible() {
@@ -277,12 +279,14 @@ public final class NavigationBar {
}
/**
* Use the bundled non cairo filled icon instead of a custom icon.
* Use the old non cairo filled icon, which is almost identical to
* the what would be the filled cairo icon.
* Custom cairo notification filled icon to fix unpatched app missing resource.
*/
private static final int fillBellCairoBlack = Utils.getResourceIdentifier(
"yt_fill_bell_black_24", "drawable");
private static final int fillBellCairoBlack = Utils.getResourceIdentifier(ResourceType.DRAWABLE,
// The bold cairo notification filled icon is present,
// but YT still has not fixed the icon not associated to the enum.
VersionCheckPatch.IS_20_31_OR_GREATER && !Settings.SETTINGS_DISABLE_BOLD_ICONS.get()
? "yt_fill_experimental_bell_vd_theme_24"
: "revanced_fill_bell_cairo_black_24");
/**
* Injection point.
@@ -290,13 +294,12 @@ public final class NavigationBar {
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static void setCairoNotificationFilledIcon(EnumMap enumMap, Enum tabActivityCairo) {
if (fillBellCairoBlack != 0) {
// Show a popup informing this fix is no longer needed to those who might care.
if (BaseSettings.DEBUG.get() && enumMap.containsKey(tabActivityCairo)) {
Logger.printException(() -> "YouTube fixed the cairo notification icons");
}
enumMap.putIfAbsent(tabActivityCairo, fillBellCairoBlack);
// Show a popup informing this fix is no longer needed to those who might care.
if (BaseSettings.DEBUG.get() && enumMap.containsKey(tabActivityCairo)) {
Logger.printException(() -> "YouTube fixed the notification icons");
}
enumMap.putIfAbsent(tabActivityCairo, fillBellCairoBlack);
}
public enum NavigationButton {

View File

@@ -3,6 +3,7 @@ package app.revanced.extension.youtube.shared
import android.app.Activity
import android.view.View
import android.view.ViewGroup
import app.revanced.extension.shared.ResourceType
import app.revanced.extension.shared.Utils
import java.lang.ref.WeakReference
@@ -19,13 +20,13 @@ class PlayerControlsVisibilityObserverImpl(
* id of the direct parent of controls_layout, R.id.youtube_controls_overlay
*/
private val controlsLayoutParentId =
Utils.getResourceIdentifier(activity, "youtube_controls_overlay", "id")
Utils.getResourceIdentifier(activity, ResourceType.ID, "youtube_controls_overlay")
/**
* id of R.id.controls_layout
*/
private val controlsLayoutId =
Utils.getResourceIdentifier(activity, "controls_layout", "id")
Utils.getResourceIdentifier(activity, ResourceType.ID, "controls_layout")
/**
* reference to the controls layout view

View File

@@ -2,7 +2,6 @@ package app.revanced.extension.youtube.shared
import app.revanced.extension.shared.Logger
import app.revanced.extension.youtube.Event
import app.revanced.extension.youtube.patches.VideoInformation
/**
* Regular player type.

View File

@@ -24,7 +24,6 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -150,9 +149,9 @@ public class SegmentPlaybackController {
private static long skipSegmentButtonEndTime;
@Nullable
private static String timeWithoutSegments;
private static int sponsorBarAbsoluteLeft;
private static int sponsorAbsoluteBarRight;
private static int sponsorBarThickness;
private static int seekbarAbsoluteLeft;
private static int seekbarAbsoluteRight;
private static int seekbarThickness;
@Nullable
private static SponsorSegment lastSegmentSkipped;
@@ -908,31 +907,13 @@ public class SegmentPlaybackController {
* injection point.
*/
@SuppressWarnings("unused")
public static void setSponsorBarRect(Object self) {
try {
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
field.setAccessible(true);
Rect rect = (Rect) Objects.requireNonNull(field.get(self));
setSponsorBarAbsoluteLeft(rect);
setSponsorBarAbsoluteRight(rect);
} catch (Exception ex) {
Logger.printException(() -> "setSponsorBarRect failure", ex);
}
}
private static void setSponsorBarAbsoluteLeft(Rect rect) {
final int left = rect.left;
if (sponsorBarAbsoluteLeft != left) {
Logger.printDebug(() -> "setSponsorBarAbsoluteLeft: " + left);
sponsorBarAbsoluteLeft = left;
}
}
private static void setSponsorBarAbsoluteRight(Rect rect) {
final int right = rect.right;
if (sponsorAbsoluteBarRight != right) {
Logger.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
sponsorAbsoluteBarRight = right;
public static void setSeekbarRectangle(Rect seekbarRect) {
final int left = seekbarRect.left;
final int right = seekbarRect.right;
if (seekbarAbsoluteLeft != left || seekbarAbsoluteRight != right) {
Logger.printDebug(() -> "setSeekbarRectangle left: " + left + " right: " + right);
seekbarAbsoluteLeft = left;
seekbarAbsoluteRight = right;
}
}
@@ -940,8 +921,8 @@ public class SegmentPlaybackController {
* injection point.
*/
@SuppressWarnings("unused")
public static void setSponsorBarThickness(int thickness) {
sponsorBarThickness = thickness;
public static void setSeekbarThickness(int thickness) {
seekbarThickness = thickness;
}
/**
@@ -951,8 +932,7 @@ public class SegmentPlaybackController {
public static String appendTimeWithoutSegments(String totalTime) {
try {
if (Settings.SB_ENABLED.get() && Settings.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.get()
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)
&& !isAdProgressTextVisible()) {
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)) {
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages
return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override
}
@@ -980,6 +960,7 @@ public class SegmentPlaybackController {
continue;
}
foundNonhighlightSegments = true;
long start = segment.start;
final long end = segment.end;
// To prevent nested segments from incorrectly counting additional time,
@@ -1011,17 +992,17 @@ public class SegmentPlaybackController {
* Injection point.
*/
@SuppressWarnings("unused")
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
public static void drawSegmentTimeBars(final Canvas canvas, final float posY) {
try {
if (segments == null || isAdProgressTextVisible()) return;
if (segments == null) return;
final long videoLength = VideoInformation.getVideoLength();
if (videoLength <= 0) return;
final int thicknessDiv2 = sponsorBarThickness / 2; // rounds down
final float top = posY - (sponsorBarThickness - thicknessDiv2);
final int thicknessDiv2 = seekbarThickness / 2; // Rounds down.
final float top = posY - (seekbarThickness - thicknessDiv2);
final float bottom = posY + thicknessDiv2;
final float videoMillisecondsToPixels = (1f / videoLength) * (sponsorAbsoluteBarRight - sponsorBarAbsoluteLeft);
final float leftPadding = sponsorBarAbsoluteLeft;
final float videoMillisecondsToPixels = (1f / videoLength) * (seekbarAbsoluteRight - seekbarAbsoluteLeft);
final float leftPadding = seekbarAbsoluteLeft;
for (SponsorSegment segment : segments) {
final float left = leftPadding + segment.start * videoMillisecondsToPixels;

View File

@@ -1,16 +1,26 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
@SuppressWarnings("unused")
public class CreateSegmentButton {
private static final int DRAWABLE_SB_LOGO = Utils.getResourceIdentifierOrThrow(
ResourceType.DRAWABLE, Utils.appIsUsingBoldIcons()
? "revanced_sb_logo_bold"
: "revanced_sb_logo"
);
@Nullable
private static PlayerControlButton instance;
@@ -31,6 +41,14 @@ public class CreateSegmentButton {
v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(),
null
);
// FIXME: Bold YT player icons are currently forced off.
// Enable this logic when the new player icons are not forced off.
ImageView icon = Utils.getChildViewByResourceName(controlsView,
"revanced_sb_create_segment_button");
if (false) {
icon.setImageResource(DRAWABLE_SB_LOGO);
}
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}

View File

@@ -16,6 +16,7 @@ import android.widget.ImageButton;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ui.Dim;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
@@ -45,8 +46,8 @@ public final class NewSegmentLayout extends FrameLayout {
final int defStyleAttr, final int defStyleRes) {
super(context, attributeSet, defStyleAttr, defStyleRes);
LayoutInflater.from(context).inflate(
getResourceIdentifierOrThrow(context, "revanced_sb_new_segment", "layout"), this, true
LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(context,
ResourceType.LAYOUT, "revanced_sb_new_segment"), this, true
);
initializeButton(
@@ -105,7 +106,7 @@ public final class NewSegmentLayout extends FrameLayout {
*/
private void initializeButton(final Context context, final String resourceIdentifierName,
final ButtonOnClickHandlerFunction handler, final String debugMessage) {
ImageButton button = findViewById(getResourceIdentifierOrThrow(context, resourceIdentifierName, "id"));
ImageButton button = findViewById(getResourceIdentifierOrThrow(context, ResourceType.ID, resourceIdentifierName));
// Add ripple effect
RippleDrawable rippleDrawable = new RippleDrawable(

View File

@@ -21,6 +21,7 @@ import androidx.annotation.NonNull;
import java.util.Objects;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
@@ -57,11 +58,10 @@ public class SkipSponsorButton extends FrameLayout {
public SkipSponsorButton(Context context, AttributeSet attributeSet, int defStyleAttr, int defStyleRes) {
super(context, attributeSet, defStyleAttr, defStyleRes);
LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(context,
"revanced_sb_skip_sponsor_button", "layout"), this, true); // layout:skip_ad_button
LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(context, ResourceType.LAYOUT, "revanced_sb_skip_sponsor_button"), this, true); // layout:skip_ad_button
setMinimumHeight(getResourceDimensionPixelSize("ad_skip_ad_button_min_height")); // dimen:ad_skip_ad_button_min_height
skipSponsorBtnContainer = Objects.requireNonNull(findViewById(getResourceIdentifierOrThrow(
context, "revanced_sb_skip_sponsor_button_container", "id"))); // id:skip_ad_button_container
context, ResourceType.ID, "revanced_sb_skip_sponsor_button_container"))); // id:skip_ad_button_container
background = new Paint();
background.setColor(getResourceColor("skip_ad_button_background_color")); // color:skip_ad_button_background_color);
@@ -72,7 +72,7 @@ public class SkipSponsorButton extends FrameLayout {
border.setStrokeWidth(getResourceDimension("ad_skip_ad_button_border_width")); // dimen:ad_skip_ad_button_border_width);
border.setStyle(Paint.Style.STROKE);
skipSponsorTextView = Objects.requireNonNull(findViewById(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button_text", "id"))); // id:skip_ad_button_text;
skipSponsorTextView = Objects.requireNonNull(findViewById(getResourceIdentifier(context, ResourceType.ID, "revanced_sb_skip_sponsor_button_text"))); // id:skip_ad_button_text;
defaultBottomMargin = getResourceDimensionPixelSize("skip_button_default_bottom_margin"); // dimen:skip_button_default_bottom_margin
ctaBottomMargin = getResourceDimensionPixelSize("skip_button_cta_bottom_margin"); // dimen:skip_button_cta_bottom_margin

View File

@@ -1,5 +1,6 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import static app.revanced.extension.shared.Utils.getResourceIdentifierOrThrow;
import android.content.Context;
@@ -15,6 +16,7 @@ import java.lang.ref.WeakReference;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
@@ -62,16 +64,17 @@ public class SponsorBlockViewController {
Context context = Utils.getContext();
RelativeLayout layout = new RelativeLayout(context);
layout.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.MATCH_PARENT));
layout.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT));
LayoutInflater.from(context).inflate(getResourceIdentifierOrThrow(
"revanced_sb_inline_sponsor_overlay", "layout"), layout);
ResourceType.LAYOUT, "revanced_sb_inline_sponsor_overlay"), layout);
inlineSponsorOverlayRef = new WeakReference<>(layout);
viewGroup.addView(layout);
viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(View parent, View child) {
// ensure SB buttons and controls are always on top, otherwise the endscreen cards can cover the skip button
// Ensure SB buttons and controls are always on top, otherwise the end-screen cards can cover the skip button.
RelativeLayout layout = inlineSponsorOverlayRef.get();
if (layout != null) {
layout.bringToFront();
@@ -83,14 +86,14 @@ public class SponsorBlockViewController {
});
youtubeOverlaysLayoutRef = new WeakReference<>(viewGroup);
skipHighlightButtonRef = new WeakReference<>(layout.findViewById(getResourceIdentifierOrThrow(
"revanced_sb_skip_highlight_button", "id")));
skipHighlightButtonRef = new WeakReference<>(Objects.requireNonNull(layout.findViewById(
getResourceIdentifier(ResourceType.ID, "revanced_sb_skip_highlight_button"))));
skipSponsorButtonRef = new WeakReference<>(layout.findViewById(getResourceIdentifierOrThrow(
"revanced_sb_skip_sponsor_button", "id")));
skipSponsorButtonRef = new WeakReference<>(Objects.requireNonNull(layout.findViewById(
getResourceIdentifier(ResourceType.ID, "revanced_sb_skip_sponsor_button"))));
NewSegmentLayout newSegmentLayout = layout.findViewById(getResourceIdentifierOrThrow(
"revanced_sb_new_segment_view", "id"));
NewSegmentLayout newSegmentLayout = Objects.requireNonNull(layout.findViewById(
getResourceIdentifier(ResourceType.ID, "revanced_sb_new_segment_view")));
newSegmentLayoutRef = new WeakReference<>(newSegmentLayout);
newSegmentLayout.updateLayout();

View File

@@ -8,6 +8,7 @@ import android.view.MotionEvent
import android.view.ViewGroup
import app.revanced.extension.shared.Logger.printDebug
import app.revanced.extension.shared.Logger.printException
import app.revanced.extension.youtube.patches.VersionCheckPatch
import app.revanced.extension.youtube.settings.Settings
import app.revanced.extension.youtube.shared.PlayerType
import app.revanced.extension.youtube.swipecontrols.controller.AudioVolumeController
@@ -237,6 +238,8 @@ class SwipeControlsHostActivity : Activity() {
*/
@Suppress("unused")
@JvmStatic
fun allowSwipeChangeVideo(original: Boolean): Boolean = Settings.SWIPE_CHANGE_VIDEO.get()
fun allowSwipeChangeVideo(original: Boolean): Boolean =
// Feature can cause crashing if forced in newer targets.
!VersionCheckPatch.IS_20_22_OR_GREATER && Settings.SWIPE_CHANGE_VIDEO.get()
}
}

View File

@@ -3,6 +3,7 @@ package app.revanced.extension.youtube.swipecontrols.controller
import android.app.Activity
import android.util.TypedValue
import android.view.ViewGroup
import app.revanced.extension.shared.ResourceType
import app.revanced.extension.shared.Utils
import app.revanced.extension.youtube.swipecontrols.misc.Rectangle
import app.revanced.extension.youtube.swipecontrols.misc.applyDimension
@@ -56,7 +57,8 @@ class SwipeZonesController(
/**
* id for R.id.player_view
*/
private val playerViewId = Utils.getResourceIdentifier(host, "player_view", "id")
private val playerViewId = Utils.getResourceIdentifier(
host, ResourceType.ID, "player_view")
/**
* current bounding rectangle of the player

View File

@@ -14,12 +14,13 @@ import android.util.AttributeSet
import android.view.HapticFeedbackConstants
import android.view.View
import android.widget.RelativeLayout
import app.revanced.extension.shared.ResourceType
import app.revanced.extension.shared.StringRef.str
import app.revanced.extension.shared.Utils
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider
import app.revanced.extension.youtube.swipecontrols.misc.SwipeControlsOverlay
import kotlin.math.min
import kotlin.math.max
import kotlin.math.min
import kotlin.math.round
/**
@@ -53,7 +54,7 @@ class SwipeControlsOverlayLayout(
// Function to retrieve drawable resources by name.
private fun getDrawable(name: String): Drawable {
val drawable = resources.getDrawable(
Utils.getResourceIdentifier(context, name, "drawable"),
Utils.getResourceIdentifier(context, ResourceType.DRAWABLE, name),
context.theme,
)
drawable.setTint(config.overlayTextColor)
@@ -86,7 +87,7 @@ class SwipeControlsOverlayLayout(
// Initialize horizontal progress bar.
val screenWidth = resources.displayMetrics.widthPixels
val layoutWidth = (screenWidth * 4 / 5).toInt() // Cap at ~360dp.
val layoutWidth = (screenWidth * 4 / 5) // Cap at ~360dp.
horizontalProgressView = HorizontalProgressView(
context,
config.overlayBackgroundOpacity,
@@ -630,7 +631,7 @@ class VerticalProgressView(
if (isMinimalStyle) {
canvas.drawText(displayText, textX, textStartY, textPaint)
} else {
val progressStartY = (iconEndY + padding).toFloat()
val progressStartY = (iconEndY + padding)
val progressEndY = textStartY - textPaint.textSize - padding
val progressHeight = progressEndY - progressStartY

View File

@@ -3,8 +3,11 @@ package app.revanced.extension.youtube.videoplayer;
import static app.revanced.extension.shared.StringRef.str;
import android.view.View;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
@@ -14,9 +17,9 @@ public class LoopVideoButton {
private static PlayerControlButton instance;
private static final int LOOP_VIDEO_ON = Utils.getResourceIdentifierOrThrow(
"revanced_loop_video_button_on", "drawable");
ResourceType.DRAWABLE, "revanced_loop_video_button_on");
private static final int LOOP_VIDEO_OFF = Utils.getResourceIdentifierOrThrow(
"revanced_loop_video_button_off", "drawable");
ResourceType.DRAWABLE,"revanced_loop_video_button_off");
/**
* Injection point.

View File

@@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.ResourceType;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.playback.quality.RememberVideoQualityPatch;

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true
android.useAndroidX = true
kotlin.code.style = official
version = 5.50.0-dev.1
version = 5.48.0-dev.9

View File

@@ -1,22 +1,22 @@
[versions]
revanced-patcher = "21.0.0"
revanced-patcher = "22.0.0-local"
# Tracking https://github.com/google/smali/issues/64.
#noinspection GradleDependency
smali = "3.0.5"
smali = "3.0.8"
# 8.3.0 causes java verifier error: https://github.com/ReVanced/revanced-patches/issues/2818.
#noinspection GradleDependency
agp = "8.2.2"
annotation = "1.9.1"
appcompat = "1.7.0"
okhttp = "5.0.0-alpha.14"
retrofit = "2.11.0"
appcompat = "1.7.1"
okhttp = "5.3.2"
retrofit = "3.0.0"
guava = "33.5.0-jre"
protobuf-javalite = "4.32.0"
protoc = "4.32.0"
protobuf = "0.9.5"
protobuf-javalite = "4.33.2"
protoc = "4.33.2"
protobuf = "0.9.6"
antlr4 = "4.13.2"
nanohttpd = "2.3.1"
apksig = "8.10.1"
apksig = "8.12.3"
[libraries]
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }

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