Compare commits

...

73 Commits

Author SHA1 Message Date
semantic-release-bot
c09255eaed chore: Release v5.27.0-dev.9 [skip ci]
# [5.27.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.8...v5.27.0-dev.9) (2025-06-09)

### Features

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

### Features

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

### Features

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

### Features

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

### Features

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

### Bug Fixes

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

### Bug Fixes

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

### Bug Fixes

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

### Features

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

### Bug Fixes

* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([65fc6b4](65fc6b43f5))
2025-06-05 19:57:52 +00:00
Nuckyz
65fc6b43f5 fix(Spotify): Prevent hiding all navigation bar buttons (#5122) 2025-06-05 16:55:33 -03:00
semantic-release-bot
2257dd90aa chore: Release v5.26.1-dev.2 [skip ci]
## [5.26.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.1...v5.26.1-dev.2) (2025-06-05)

### Bug Fixes

* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([4b8499f](4b8499ff2c))
2025-06-05 19:12:08 +00:00
LisoUseInAIKyrios
4b8499ff2c fix(YouTube - Hide layout components): Remove broken option 'Hide comments emoji picker' (#5121) 2025-06-05 21:09:25 +02:00
Nuckyz
bde3fda972 refactor(Spotify): Add extensions debug logging (#5110) 2025-06-05 16:04:02 -03:00
semantic-release-bot
e2e07b5cb2 chore: Release v5.26.1-dev.1 [skip ci]
## [5.26.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.26.1-dev.1) (2025-06-05)

### Bug Fixes

* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([9d10ab6](9d10ab6c00))
2025-06-05 09:24:29 +00:00
LisoUseInAIKyrios
9d10ab6c00 fix(YouTube - Hide Shorts components): Disable A/B player flags that prevents hiding buttons 2025-06-05 11:21:05 +02:00
github-actions[bot]
d7644152fd chore: Sync translations (#5116) 2025-06-05 11:19:38 +02:00
LisoUseInAIKyrios
9be21f4824 refactor(YouTube - Hide Shorts components): Rename 'Hide comment panel' to 'Hide preview comment' 2025-06-05 11:08:24 +02:00
semantic-release-bot
a2eae0bf04 chore: Release v5.26.0 [skip ci]
# [5.26.0](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0) (2025-06-04)

### Bug Fixes

* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([bc45433](bc45433dcb))
* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([1d0c568](1d0c56819b))
* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([ff903ba](ff903ba9ac))

### Features

* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([49ae0df](49ae0df224))
* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([ce5385b](ce5385b28e))
* **Sync for Reddit:** Add `Fix post thumbnails` patch ([7a53580](7a53580380))
* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([e435b33](e435b33593))
* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([a320e35](a320e35c32))
2025-06-04 14:05:56 +00:00
LisoUseInAIKyrios
679354b5b3 chore: Merge branch dev to main (#5079) 2025-06-04 16:02:42 +02:00
semantic-release-bot
91dec21033 chore: Release v5.26.0-dev.8 [skip ci]
# [5.26.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.7...v5.26.0-dev.8) (2025-06-04)

### Bug Fixes

* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([1d0c568](1d0c56819b))
2025-06-04 11:48:26 +00:00
LisoUseInAIKyrios
1d0c56819b fix(YouTube - Hide Shorts components): Disable A/B player that prevents hiding buttons (#5104) 2025-06-04 13:45:48 +02:00
github-actions[bot]
4410816c22 chore: Sync translations (#5106) 2025-06-04 13:44:35 +02:00
semantic-release-bot
7e4e48bc9f chore: Release v5.26.0-dev.7 [skip ci]
# [5.26.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.6...v5.26.0-dev.7) (2025-06-04)

### Features

* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([e435b33](e435b33593))
2025-06-04 07:09:39 +00:00
LisoUseInAIKyrios
e435b33593 feat(YouTube - Hide Shorts components): Add option to hide comment panel (#5102) 2025-06-04 09:06:27 +02:00
semantic-release-bot
bf288b83ae chore: Release v5.26.0-dev.6 [skip ci]
# [5.26.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.5...v5.26.0-dev.6) (2025-06-03)

### Features

* **Sync for Reddit:** Add `Fix post thumbnails` patch ([7a53580](7a53580380))
2025-06-03 19:50:57 +00:00
kolpazar
7a53580380 feat(Sync for Reddit): Add Fix post thumbnails patch 2025-06-03 21:48:24 +02:00
semantic-release-bot
6439efa2a9 chore: Release v5.26.0-dev.5 [skip ci]
# [5.26.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.4...v5.26.0-dev.5) (2025-06-03)

### Bug Fixes

* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([bc45433](bc45433dcb))
2025-06-03 08:04:58 +00:00
Cilly Leang
bc45433dcb fix(Spotify - Custom theme): Apply accent color in more places (#5039)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-06-03 10:02:15 +02:00
github-actions[bot]
8871803e83 chore: Sync translations (#5095) 2025-06-03 09:59:31 +02:00
semantic-release-bot
18954a0285 chore: Release v5.26.0-dev.4 [skip ci]
# [5.26.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.3...v5.26.0-dev.4) (2025-06-03)

### Features

* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([ce5385b](ce5385b28e))
2025-06-03 07:18:30 +00:00
Nuckyz
ce5385b28e feat(Spotify): Add Hide Create button patch (#5062) 2025-06-03 09:15:52 +02:00
dependabot[bot]
3f4cdf6f83 chore(deps-dev): bump semantic-release from 24.2.1 to 24.2.5 (#5086) 2025-06-01 20:57:25 +02:00
semantic-release-bot
094b4a1ea8 chore: Release v5.26.0-dev.3 [skip ci]
# [5.26.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.2...v5.26.0-dev.3) (2025-06-01)

### Features

* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([a320e35](a320e35c32))
2025-06-01 09:15:51 +00:00
MarcaD
a320e35c32 feat(YouTube - Playback Speed): Use modern custom speed dialog (#5069)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-06-01 11:12:56 +02:00
semantic-release-bot
5bf5a2d2db chore: Release v5.26.0-dev.2 [skip ci]
# [5.26.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.1...v5.26.0-dev.2) (2025-06-01)

### Bug Fixes

* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([ff903ba](ff903ba9ac))
2025-06-01 07:01:35 +00:00
alieRN
ff903ba9ac fix(YouTube): Support A/B Shorts layout for RYD and component hiding (#5081) 2025-06-01 08:59:09 +02:00
github-actions[bot]
1079a54dbe chore: Sync translations (#5084) 2025-06-01 08:58:37 +02:00
ILoveOpenSourceApplications
2b0e3b4553 refactor: Make strings consistent (#5075) 2025-05-31 10:25:04 +02:00
semantic-release-bot
0265a7791b chore: Release v5.26.0-dev.1 [skip ci]
# [5.26.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0-dev.1) (2025-05-30)

### Features

* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([49ae0df](49ae0df224))
2025-05-30 20:53:45 +00:00
ByteEVM
49ae0df224 feat(Proton Mail): Add Remove free accounts limit patch (#4970)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-30 22:51:12 +02:00
semantic-release-bot
e279491724 chore: Release v5.25.0 [skip ci]
# [5.25.0](https://github.com/ReVanced/revanced-patches/compare/v5.24.0...v5.25.0) (2025-05-29)

### Bug Fixes

* **Disable Pairip license check:** Change patch to default off ([798596f](798596fd83))
* **Hide ADB status:** Resolve app crash on startup ([#5029](https://github.com/ReVanced/revanced-patches/issues/5029)) ([2984d73](2984d7362d))
* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([517368e](517368eda7))
* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([b712f38](b712f38017))
* **YouTube - GmsCore support:** Restore patch functionality from prior merge ([b6047fa](b6047fa6b3))
* **YouTube - Hide ads:** Hide new type of general ad ([#5004](https://github.com/ReVanced/revanced-patches/issues/5004)) ([bc0c3c4](bc0c3c452d))
* **YouTube - Open Shorts in regular player:** Do not exit app when pressing back button in regular player ([#5020](https://github.com/ReVanced/revanced-patches/issues/5020)) ([4ab1f0c](4ab1f0cfa9))
* **YouTube:** Better handle incorrect duplicate translations ([8d61ba9](8d61ba90c3))
* **Yuka - Unlock premium:** Remove broken patch that is no longer supported ([#5018](https://github.com/ReVanced/revanced-patches/issues/5018)) ([e286dab](e286dab74e))

### Features

* Add `Disable pairip license check` patch ([#4927](https://github.com/ReVanced/revanced-patches/issues/4927)) ([dea7108](dea7108c45))
* **Messenger:** Add `Remove Meta AI` patch ([#4945](https://github.com/ReVanced/revanced-patches/issues/4945)) ([52b9dc5](52b9dc5c9f))
* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([02373b0](02373b0bd2))
* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([795016a](795016abce))
* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([bf1f26d](bf1f26d8bb))
* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([83c148a](83c148addc))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([584b00f](584b00fd87))
* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([443b54b](443b54bf09))
* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([5c8ed05](5c8ed05727))
2025-05-29 07:26:20 +00:00
LisoUseInAIKyrios
495260fe2b chore: Merge branch dev to main (#5010) 2025-05-29 09:22:53 +02:00
github-actions[bot]
40f069fff7 chore: Sync translations (#5066) 2025-05-29 09:20:59 +02:00
semantic-release-bot
de263c1061 chore: Release v5.25.0-dev.14 [skip ci]
# [5.25.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.13...v5.25.0-dev.14) (2025-05-29)

### Features

* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([bf1f26d](bf1f26d8bb))
2025-05-29 07:17:42 +00:00
scruz
bf1f26d8bb feat(Threads): Hide Ads (#5064) 2025-05-29 09:14:24 +02:00
semantic-release-bot
0ee2ed72d4 chore: Release v5.25.0-dev.13 [skip ci]
# [5.25.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.12...v5.25.0-dev.13) (2025-05-28)

### Features

* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([02373b0](02373b0bd2))
2025-05-28 20:41:45 +00:00
hoodles
02373b0bd2 feat(Prime Video): Add Rename shared permissions patch (#5049) 2025-05-28 22:38:09 +02:00
github-actions[bot]
97c8e2489d chore: Sync translations (#5061) 2025-05-28 22:37:18 +02:00
semantic-release-bot
08b2b2e104 chore: Release v5.25.0-dev.12 [skip ci]
# [5.25.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.11...v5.25.0-dev.12) (2025-05-28)

### Features

* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([443b54b](443b54bf09))
2025-05-28 10:02:34 +00:00
github-actions[bot]
6b386b67d2 chore: Sync translations (#5055) 2025-05-28 11:58:56 +02:00
Nuckyz
f8343ae9f6 refactor(Spotify): Improve protobuf array list mutability patch (#5053)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-05-28 11:55:25 +02:00
LisoUseInAIKyrios
3ba791ac7d refactor(YouTube - Enabling debugging): Adjust logger formatting to preserve backwards compatibility (#5054) 2025-05-28 11:55:02 +02:00
MarcaD
443b54bf09 feat(YouTube - Swipe controls): Add separate color settings for the brightness and volume bars (#5043) 2025-05-28 11:54:28 +02:00
semantic-release-bot
53587f190d chore: Release v5.25.0-dev.11 [skip ci]
# [5.25.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.10...v5.25.0-dev.11) (2025-05-27)

### Features

* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([83c148a](83c148addc))
* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([5c8ed05](5c8ed05727))
2025-05-27 07:55:39 +00:00
MarcaD
83c148addc feat(YouTube - Enable debugging): Add settings menu to share debug logs (#5021)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-05-27 09:52:24 +02:00
MarcaD
5c8ed05727 feat(YouTube): Add Disable haptic feedback patch (#5033) 2025-05-27 09:52:01 +02:00
semantic-release-bot
33833d7a1e chore: Release v5.25.0-dev.10 [skip ci]
# [5.25.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.9...v5.25.0-dev.10) (2025-05-27)

### Bug Fixes

* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([517368e](517368eda7))
* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([b712f38](b712f38017))
2025-05-27 06:51:05 +00:00
LisoUseInAIKyrios
b712f38017 fix(Spotify Lite): Remove obsolete Enable on demand patch (#5046) 2025-05-27 08:47:56 +02:00
LisoUseInAIKyrios
517368eda7 fix(Messenger): Remove outdated Disable switching emoji to sticker patch (#5044) 2025-05-27 08:47:09 +02:00
LisoUseInAIKyrios
2093c0c175 chore: fix api dump 2025-05-26 12:36:16 +02:00
176 changed files with 5435 additions and 1727 deletions

View File

@@ -1,3 +1,226 @@
# [5.27.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.8...v5.27.0-dev.9) (2025-06-09)
### Features
* **Messenger:** Add `Hide Facebook button` patch ([#5057](https://github.com/ReVanced/revanced-patches/issues/5057)) ([9175b23](https://github.com/ReVanced/revanced-patches/commit/9175b23e8360d13c8c1c9c8602ca0b5931d13627))
# [5.27.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.7...v5.27.0-dev.8) (2025-06-09)
### Features
* Add `Hide app icon` patch ([#4977](https://github.com/ReVanced/revanced-patches/issues/4977)) ([92311b8](https://github.com/ReVanced/revanced-patches/commit/92311b8e5675f3d4b80ed690d34b699fb847e3cd))
# [5.27.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.6...v5.27.0-dev.7) (2025-06-08)
### Features
* **YouTube - Hide player overlay buttons:** Add in app setting for "Hide player control buttons background" ([#5147](https://github.com/ReVanced/revanced-patches/issues/5147)) ([dd8afa2](https://github.com/ReVanced/revanced-patches/commit/dd8afa2b07b50be24d764c0f6ddc9e1bbdb91bf1))
# [5.27.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.5...v5.27.0-dev.6) (2025-06-08)
### Features
* **YouTube - Hide Shorts components:** Add hide 'New posts' button ([ac6b916](https://github.com/ReVanced/revanced-patches/commit/ac6b916c0c212167c4645e2110500dc811b3e54a))
# [5.27.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.4...v5.27.0-dev.5) (2025-06-08)
### Features
* **Google Photos:** Add `Enable DCIM folders backup control` patch ([#5138](https://github.com/ReVanced/revanced-patches/issues/5138)) ([328d232](https://github.com/ReVanced/revanced-patches/commit/328d232fe77406fa93a14768fc66e7b998506fba))
# [5.27.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.3...v5.27.0-dev.4) (2025-06-06)
### Bug Fixes
* **Bandcamp - Remove play limits:** Support latest app version ([#5124](https://github.com/ReVanced/revanced-patches/issues/5124)) ([863e92b](https://github.com/ReVanced/revanced-patches/commit/863e92b20ad6682f10524e475ed18f879048ecae))
# [5.27.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.2...v5.27.0-dev.3) (2025-06-06)
### Bug Fixes
* **Spotify:** `Hide Create button` patch failing in edge cases ([#5131](https://github.com/ReVanced/revanced-patches/issues/5131)) ([0923600](https://github.com/ReVanced/revanced-patches/commit/0923600739a126329fc62100b500216860d7005e))
# [5.27.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.27.0-dev.1...v5.27.0-dev.2) (2025-06-06)
### Bug Fixes
* **YouTube - Video quality:** Remove non-functional Shorts 144p default quality ([3113cd6](https://github.com/ReVanced/revanced-patches/commit/3113cd6d092952c8657454452f34c0ae85358ec9))
# [5.27.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.3...v5.27.0-dev.1) (2025-06-05)
### Features
* **YouTube - Theme:** Add option for black and white splash screen animation ([#5119](https://github.com/ReVanced/revanced-patches/issues/5119)) ([42db0c2](https://github.com/ReVanced/revanced-patches/commit/42db0c2e36fefccdbeaa072edcec48b1e05b6270))
## [5.26.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.2...v5.26.1-dev.3) (2025-06-05)
### Bug Fixes
* **Spotify:** Prevent hiding all navigation bar buttons ([#5122](https://github.com/ReVanced/revanced-patches/issues/5122)) ([8afbef0](https://github.com/ReVanced/revanced-patches/commit/8afbef01343c1e3e6e7e4a4cec6319aebfa4b11c))
## [5.26.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.1-dev.1...v5.26.1-dev.2) (2025-06-05)
### Bug Fixes
* **YouTube - Hide layout components:** Remove broken option 'Hide comments emoji picker' ([#5121](https://github.com/ReVanced/revanced-patches/issues/5121)) ([9a6a639](https://github.com/ReVanced/revanced-patches/commit/9a6a639c4905b00d6dffb0923c839c8e3ae54d0c))
## [5.26.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.26.0...v5.26.1-dev.1) (2025-06-05)
### Bug Fixes
* **YouTube - Hide Shorts components:** Disable A/B player flags that prevents hiding buttons ([bef0dac](https://github.com/ReVanced/revanced-patches/commit/bef0dacac54caf1ca9511d7bc19b19140ccb4eaf))
# [5.26.0](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0) (2025-06-04)
### Bug Fixes
* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([9357887](https://github.com/ReVanced/revanced-patches/commit/9357887b6fca7aaf34dfb0163645b6a998e1db76))
* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([835b7bd](https://github.com/ReVanced/revanced-patches/commit/835b7bd7bd667abd632822c98898972e5124dbb6))
* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([8ecacaa](https://github.com/ReVanced/revanced-patches/commit/8ecacaad27162d9380014a9a13ac9220b12257b2))
### Features
* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([b0440ad](https://github.com/ReVanced/revanced-patches/commit/b0440ad6af0e190e516974ce896dcc54c8d2e122))
* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([3201681](https://github.com/ReVanced/revanced-patches/commit/32016819d2adbdfdd5e028941d56feda36d20b00))
* **Sync for Reddit:** Add `Fix post thumbnails` patch ([e1ec30c](https://github.com/ReVanced/revanced-patches/commit/e1ec30c5b07560a39d7b8ab293b0c1f39fd59ef2))
* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([22b9bee](https://github.com/ReVanced/revanced-patches/commit/22b9beedd3243a8d6a5635f591b91cdcf307be37))
* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([9a1e6ca](https://github.com/ReVanced/revanced-patches/commit/9a1e6ca178d9833ee2c681fb130b9290a4e89cd8))
# [5.26.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.7...v5.26.0-dev.8) (2025-06-04)
### Bug Fixes
* **YouTube - Hide Shorts components:** Disable A/B player that prevents hiding buttons ([#5104](https://github.com/ReVanced/revanced-patches/issues/5104)) ([835b7bd](https://github.com/ReVanced/revanced-patches/commit/835b7bd7bd667abd632822c98898972e5124dbb6))
# [5.26.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.6...v5.26.0-dev.7) (2025-06-04)
### Features
* **YouTube - Hide Shorts components:** Add option to hide comment panel ([#5102](https://github.com/ReVanced/revanced-patches/issues/5102)) ([22b9bee](https://github.com/ReVanced/revanced-patches/commit/22b9beedd3243a8d6a5635f591b91cdcf307be37))
# [5.26.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.5...v5.26.0-dev.6) (2025-06-03)
### Features
* **Sync for Reddit:** Add `Fix post thumbnails` patch ([e1ec30c](https://github.com/ReVanced/revanced-patches/commit/e1ec30c5b07560a39d7b8ab293b0c1f39fd59ef2))
# [5.26.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.4...v5.26.0-dev.5) (2025-06-03)
### Bug Fixes
* **Spotify - Custom theme:** Apply accent color in more places ([#5039](https://github.com/ReVanced/revanced-patches/issues/5039)) ([9357887](https://github.com/ReVanced/revanced-patches/commit/9357887b6fca7aaf34dfb0163645b6a998e1db76))
# [5.26.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.3...v5.26.0-dev.4) (2025-06-03)
### Features
* **Spotify:** Add `Hide Create button` patch ([#5062](https://github.com/ReVanced/revanced-patches/issues/5062)) ([3201681](https://github.com/ReVanced/revanced-patches/commit/32016819d2adbdfdd5e028941d56feda36d20b00))
# [5.26.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.2...v5.26.0-dev.3) (2025-06-01)
### Features
* **YouTube - Playback Speed:** Use modern custom speed dialog ([#5069](https://github.com/ReVanced/revanced-patches/issues/5069)) ([9a1e6ca](https://github.com/ReVanced/revanced-patches/commit/9a1e6ca178d9833ee2c681fb130b9290a4e89cd8))
# [5.26.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.26.0-dev.1...v5.26.0-dev.2) (2025-06-01)
### Bug Fixes
* **YouTube:** Support A/B Shorts layout for RYD and component hiding ([#5081](https://github.com/ReVanced/revanced-patches/issues/5081)) ([8ecacaa](https://github.com/ReVanced/revanced-patches/commit/8ecacaad27162d9380014a9a13ac9220b12257b2))
# [5.26.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.25.0...v5.26.0-dev.1) (2025-05-30)
### Features
* **Proton Mail:** Add `Remove free accounts limit` patch ([#4970](https://github.com/ReVanced/revanced-patches/issues/4970)) ([b0440ad](https://github.com/ReVanced/revanced-patches/commit/b0440ad6af0e190e516974ce896dcc54c8d2e122))
# [5.25.0](https://github.com/ReVanced/revanced-patches/compare/v5.24.0...v5.25.0) (2025-05-29)
### Bug Fixes
* **Disable Pairip license check:** Change patch to default off ([74b6a94](https://github.com/ReVanced/revanced-patches/commit/74b6a94577ac3f73b04bd0cce98fb7011a6607fd))
* **Hide ADB status:** Resolve app crash on startup ([#5029](https://github.com/ReVanced/revanced-patches/issues/5029)) ([1abebd5](https://github.com/ReVanced/revanced-patches/commit/1abebd5f3b73250c6638d2d8a274b92ea8268924))
* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([7b182ca](https://github.com/ReVanced/revanced-patches/commit/7b182cab825ee3a4a3ca528c744c9d2a351c7cf8))
* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([4886d47](https://github.com/ReVanced/revanced-patches/commit/4886d47506c94b03c1f190ecc4947d3d91df6a47))
* **YouTube - GmsCore support:** Restore patch functionality from prior merge ([7686bbe](https://github.com/ReVanced/revanced-patches/commit/7686bbe975644e1e582fa52f166879da5694ed93))
* **YouTube - Hide ads:** Hide new type of general ad ([#5004](https://github.com/ReVanced/revanced-patches/issues/5004)) ([37e59d2](https://github.com/ReVanced/revanced-patches/commit/37e59d2771528c631dc13e73dac095fec95c6485))
* **YouTube - Open Shorts in regular player:** Do not exit app when pressing back button in regular player ([#5020](https://github.com/ReVanced/revanced-patches/issues/5020)) ([3384f8d](https://github.com/ReVanced/revanced-patches/commit/3384f8dd0ff2a345f2e387f4ed1570079a83ccb6))
* **YouTube:** Better handle incorrect duplicate translations ([20abac5](https://github.com/ReVanced/revanced-patches/commit/20abac52121fbecb65d87d0982f3380e1cf4e20e))
* **Yuka - Unlock premium:** Remove broken patch that is no longer supported ([#5018](https://github.com/ReVanced/revanced-patches/issues/5018)) ([fac6e59](https://github.com/ReVanced/revanced-patches/commit/fac6e59d281e21e57abdcfc899cd1aeb18e5c2b8))
### Features
* Add `Disable pairip license check` patch ([#4927](https://github.com/ReVanced/revanced-patches/issues/4927)) ([42d2c27](https://github.com/ReVanced/revanced-patches/commit/42d2c277982ef63e6ad42d85e46f13c3ab50243c))
* **Messenger:** Add `Remove Meta AI` patch ([#4945](https://github.com/ReVanced/revanced-patches/issues/4945)) ([012dff7](https://github.com/ReVanced/revanced-patches/commit/012dff7b6511b9e519ccac96f6713cf1a1b327b4))
* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([80f1fc6](https://github.com/ReVanced/revanced-patches/commit/80f1fc629e30e391bd5877f07dbdf4b6613bd1cf))
* **Spotify:** Add `Fix Facebook login` patch ([#5023](https://github.com/ReVanced/revanced-patches/issues/5023)) ([34932dc](https://github.com/ReVanced/revanced-patches/commit/34932dc43933d346a5a3adadc62c0dbd38a633b5))
* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([3c4cecb](https://github.com/ReVanced/revanced-patches/commit/3c4cecb966c2f99bfde99552686dda19ade5f67e))
* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([1ec4a88](https://github.com/ReVanced/revanced-patches/commit/1ec4a88464a2a2810c02cf072950b618d183779a))
* **YouTube - Settings:** Add a color picker ([#4981](https://github.com/ReVanced/revanced-patches/issues/4981)) ([1e0e398](https://github.com/ReVanced/revanced-patches/commit/1e0e398574329173aff11a4dc9acfc3fcdeabe16))
* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([80f50e8](https://github.com/ReVanced/revanced-patches/commit/80f50e8c50ca6a8366b7fd7b01459fb16fa1074a))
* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([bbe7974](https://github.com/ReVanced/revanced-patches/commit/bbe79744a513c96f9016476e8435f999e94c45d7))
# [5.25.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.13...v5.25.0-dev.14) (2025-05-29)
### Features
* **Threads:** Hide Ads ([#5064](https://github.com/ReVanced/revanced-patches/issues/5064)) ([3c4cecb](https://github.com/ReVanced/revanced-patches/commit/3c4cecb966c2f99bfde99552686dda19ade5f67e))
# [5.25.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.12...v5.25.0-dev.13) (2025-05-28)
### Features
* **Prime Video:** Add `Rename shared permissions` patch ([#5049](https://github.com/ReVanced/revanced-patches/issues/5049)) ([80f1fc6](https://github.com/ReVanced/revanced-patches/commit/80f1fc629e30e391bd5877f07dbdf4b6613bd1cf))
# [5.25.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.11...v5.25.0-dev.12) (2025-05-28)
### Features
* **YouTube - Swipe controls:** Add separate color settings for the brightness and volume bars ([#5043](https://github.com/ReVanced/revanced-patches/issues/5043)) ([80f50e8](https://github.com/ReVanced/revanced-patches/commit/80f50e8c50ca6a8366b7fd7b01459fb16fa1074a))
# [5.25.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.10...v5.25.0-dev.11) (2025-05-27)
### Features
* **YouTube - Enable debugging:** Add settings menu to share debug logs ([#5021](https://github.com/ReVanced/revanced-patches/issues/5021)) ([1ec4a88](https://github.com/ReVanced/revanced-patches/commit/1ec4a88464a2a2810c02cf072950b618d183779a))
* **YouTube:** Add `Disable haptic feedback` patch ([#5033](https://github.com/ReVanced/revanced-patches/issues/5033)) ([bbe7974](https://github.com/ReVanced/revanced-patches/commit/bbe79744a513c96f9016476e8435f999e94c45d7))
# [5.25.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.9...v5.25.0-dev.10) (2025-05-27)
### Bug Fixes
* **Messenger:** Remove outdated `Disable switching emoji to sticker` patch ([#5044](https://github.com/ReVanced/revanced-patches/issues/5044)) ([7b182ca](https://github.com/ReVanced/revanced-patches/commit/7b182cab825ee3a4a3ca528c744c9d2a351c7cf8))
* **Spotify Lite:** Remove obsolete `Enable on demand` patch ([#5046](https://github.com/ReVanced/revanced-patches/issues/5046)) ([4886d47](https://github.com/ReVanced/revanced-patches/commit/4886d47506c94b03c1f190ecc4947d3d91df6a47))
# [5.25.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.8...v5.25.0-dev.9) (2025-05-26) # [5.25.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.25.0-dev.8...v5.25.0-dev.9) (2025-05-26)

View File

@@ -2,13 +2,15 @@ package app.revanced.extension.messenger.metaai;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class RemoveMetaAIPatch { public class RemoveMetaAIPatch {
public static boolean overrideConfigBool(long id, boolean value) { public static boolean overrideBooleanFlag(long id, boolean value) {
// It seems like all configs starting with 363219 are related to Meta AI. // This catches all flag IDs related to Meta AI.
// A list of specific ones that need disabling would probably be better, // The IDs change slightly with every update,
// but these config numbers seem to change slightly with each update. // so to work around this, IDs from different versions were compared
// These first 6 digits don't though. // to find what they have in common, which turned out to be those first bits.
if (Long.toString(id).startsWith("363219")) // TODO: Find the specific flags that we care about and patch the code they control instead.
if ((id & 0x7FFFFFC000000000L) == 0x810A8000000000L) {
return false; return false;
}
return value; return value;
} }

View File

@@ -1,15 +1,26 @@
package app.revanced.extension.shared; package app.revanced.extension.shared;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_STACKTRACE;
import static app.revanced.extension.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.settings.BaseSettings;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import static app.revanced.extension.shared.settings.BaseSettings.*; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* ReVanced specific logger. Logging is done to standard device log (accessible thru ADB),
* and additionally accessible thru {@link LogBufferManager}.
*
* All methods are thread safe.
*/
public class Logger { public class Logger {
/** /**
@@ -17,99 +28,159 @@ public class Logger {
*/ */
@FunctionalInterface @FunctionalInterface
public interface LogMessage { public interface LogMessage {
/**
* @return Logger string message. This method is only called if logging is enabled.
*/
@NonNull @NonNull
String buildMessageString(); String buildMessageString();
}
/** private enum LogLevel {
* @return For outer classes, this returns {@link Class#getSimpleName()}. DEBUG,
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class. INFO,
* <br> ERROR
* For example, each of these classes return 'SomethingView': }
* <code>
* com.company.SomethingView
* com.company.SomethingView$StaticClass
* com.company.SomethingView$1
* </code>
*/
private String findOuterClassSimpleName() {
var selfClass = this.getClass();
String fullClassName = selfClass.getName(); /**
final int dollarSignIndex = fullClassName.indexOf('$'); * Log tag prefix. Only used for system logging.
if (dollarSignIndex < 0) { */
return selfClass.getSimpleName(); // Already an outer class. private static final String REVANCED_LOG_TAG_PREFIX = "revanced: ";
private static final String LOGGER_CLASS_NAME = Logger.class.getName();
/**
* @return For outer classes, this returns {@link Class#getSimpleName()}.
* For static, inner, or anonymous classes, this returns the simple name of the enclosing class.
* <br>
* For example, each of these classes returns 'SomethingView':
* <code>
* com.company.SomethingView
* com.company.SomethingView$StaticClass
* com.company.SomethingView$1
* </code>
*/
private static String getOuterClassSimpleName(Object obj) {
Class<?> logClass = obj.getClass();
String fullClassName = logClass.getName();
final int dollarSignIndex = fullClassName.indexOf('$');
if (dollarSignIndex < 0) {
return logClass.getSimpleName(); // Already an outer class.
}
// Class is inner, static, or anonymous.
// Parse the simple name full name.
// A class with no package returns index of -1, but incrementing gives index zero which is correct.
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1;
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex);
}
/**
* Internal method to handle logging to Android Log and {@link LogBufferManager}.
* Appends the log message, stack trace (if enabled), and exception (if present) to logBuffer
* with class name but without 'revanced:' prefix.
*
* @param logLevel The log level.
* @param message Log message object.
* @param ex Optional exception.
* @param includeStackTrace If the current stack should be included.
* @param showToast If a toast is to be shown.
*/
private static void logInternal(LogLevel logLevel, LogMessage message, @Nullable Throwable ex,
boolean includeStackTrace, boolean showToast) {
// It's very important that no Settings are used in this method,
// as this code is used when a context is not set and thus referencing
// a setting will crash the app.
String messageString = message.buildMessageString();
String className = getOuterClassSimpleName(message);
String logText = messageString;
// Append exception message if present.
if (ex != null) {
var exceptionMessage = ex.getMessage();
if (exceptionMessage != null) {
logText += "\nException: " + exceptionMessage;
} }
}
// Class is inner, static, or anonymous. if (includeStackTrace) {
// Parse the simple name full name. var sw = new StringWriter();
// A class with no package returns index of -1, but incrementing gives index zero which is correct. new Throwable().printStackTrace(new PrintWriter(sw));
final int simpleClassNameStartIndex = fullClassName.lastIndexOf('.') + 1; String stackTrace = sw.toString();
return fullClassName.substring(simpleClassNameStartIndex, dollarSignIndex); // Remove the stacktrace elements of this class.
final int loggerIndex = stackTrace.lastIndexOf(LOGGER_CLASS_NAME);
final int loggerBegins = stackTrace.indexOf('\n', loggerIndex);
logText += stackTrace.substring(loggerBegins);
}
// Do not include "revanced:" prefix in clipboard logs.
String managerToastString = className + ": " + logText;
LogBufferManager.appendToLogBuffer(managerToastString);
String logTag = REVANCED_LOG_TAG_PREFIX + className;
switch (logLevel) {
case DEBUG:
if (ex == null) Log.d(logTag, logText);
else Log.d(logTag, logText, ex);
break;
case INFO:
if (ex == null) Log.i(logTag, logText);
else Log.i(logTag, logText, ex);
break;
case ERROR:
if (ex == null) Log.e(logTag, logText);
else Log.e(logTag, logText, ex);
break;
}
if (showToast) {
Utils.showToastLong(managerToastString);
} }
} }
private static final String REVANCED_LOG_PREFIX = "revanced: ";
/** /**
* Logs debug messages under the outer class name of the code calling this method. * Logs debug messages under the outer class name of the code calling this method.
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()} * <p>
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled. * Whenever possible, the log string should be constructed entirely inside
* {@link LogMessage#buildMessageString()} so the performance cost of
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
*/ */
public static void printDebug(@NonNull LogMessage message) { public static void printDebug(LogMessage message) {
printDebug(message, null); printDebug(message, null);
} }
/** /**
* Logs debug messages under the outer class name of the code calling this method. * Logs debug messages under the outer class name of the code calling this method.
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()} * <p>
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled. * Whenever possible, the log string should be constructed entirely inside
* {@link LogMessage#buildMessageString()} so the performance cost of
* building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
*/ */
public static void printDebug(@NonNull LogMessage message, @Nullable Exception ex) { public static void printDebug(LogMessage message, @Nullable Exception ex) {
if (DEBUG.get()) { if (DEBUG.get()) {
String logMessage = message.buildMessageString(); logInternal(LogLevel.DEBUG, message, ex, DEBUG_STACKTRACE.get(), false);
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName();
if (DEBUG_STACKTRACE.get()) {
var builder = new StringBuilder(logMessage);
var sw = new StringWriter();
new Throwable().printStackTrace(new PrintWriter(sw));
builder.append('\n').append(sw);
logMessage = builder.toString();
}
if (ex == null) {
Log.d(logTag, logMessage);
} else {
Log.d(logTag, logMessage, ex);
}
} }
} }
/** /**
* Logs information messages using the outer class name of the code calling this method. * Logs information messages using the outer class name of the code calling this method.
*/ */
public static void printInfo(@NonNull LogMessage message) { public static void printInfo(LogMessage message) {
printInfo(message, null); printInfo(message, null);
} }
/** /**
* Logs information messages using the outer class name of the code calling this method. * Logs information messages using the outer class name of the code calling this method.
*/ */
public static void printInfo(@NonNull LogMessage message, @Nullable Exception ex) { public static void printInfo(LogMessage message, @Nullable Exception ex) {
String logTag = REVANCED_LOG_PREFIX + message.findOuterClassSimpleName(); logInternal(LogLevel.INFO, message, ex, DEBUG_STACKTRACE.get(), false);
String logMessage = message.buildMessageString();
if (ex == null) {
Log.i(logTag, logMessage);
} else {
Log.i(logTag, logMessage, ex);
}
} }
/** /**
* Logs exceptions under the outer class name of the code calling this method. * Logs exceptions under the outer class name of the code calling this method.
* Appends the log message, exception (if present), and toast message (if enabled) to logBuffer.
*/ */
public static void printException(@NonNull LogMessage message) { public static void printException(LogMessage message) {
printException(message, null); printException(message, null);
} }
@@ -122,35 +193,23 @@ public class Logger {
* @param message log message * @param message log message
* @param ex exception (optional) * @param ex exception (optional)
*/ */
public static void printException(@NonNull LogMessage message, @Nullable Throwable ex) { public static void printException(LogMessage message, @Nullable Throwable ex) {
String messageString = message.buildMessageString(); logInternal(LogLevel.ERROR, message, ex, DEBUG_STACKTRACE.get(), DEBUG_TOAST_ON_ERROR.get());
String outerClassSimpleName = message.findOuterClassSimpleName();
String logMessage = REVANCED_LOG_PREFIX + outerClassSimpleName;
if (ex == null) {
Log.e(logMessage, messageString);
} else {
Log.e(logMessage, messageString, ex);
}
if (DEBUG_TOAST_ON_ERROR.get()) {
Utils.showToastLong(outerClassSimpleName + ": " + messageString);
}
} }
/** /**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized. * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used. * Normally this method should not be used.
*/ */
public static void initializationInfo(@NonNull Class<?> callingClass, @NonNull String message) { public static void initializationInfo(LogMessage message) {
Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message); logInternal(LogLevel.INFO, message, null, false, false);
} }
/** /**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized. * Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used. * Normally this method should not be used.
*/ */
public static void initializationException(@NonNull Class<?> callingClass, @NonNull String message, public static void initializationException(LogMessage message, @Nullable Exception ex) {
@Nullable Exception ex) { logInternal(LogLevel.ERROR, message, ex, false, false);
Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex);
} }
} }

View File

@@ -363,15 +363,17 @@ public class Utils {
public static Context getContext() { public static Context getContext() {
if (context == null) { if (context == null) {
Logger.initializationException(Utils.class, "Context is not set by extension hook, returning null", null); Logger.initializationException(() -> "Context is not set by extension hook, returning null", null);
} }
return context; return context;
} }
public static void setContext(Context appContext) { public static void setContext(Context appContext) {
// Intentionally use logger before context is set,
// to expose any bugs in the 'no context available' logger method.
Logger.initializationInfo(() -> "Set context: " + appContext);
// Must initially set context to check the app language. // Must initially set context to check the app language.
context = appContext; context = appContext;
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get(); AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
if (language != AppLanguage.DEFAULT) { if (language != AppLanguage.DEFAULT) {
@@ -383,8 +385,9 @@ public class Utils {
} }
} }
public static void setClipboard(@NonNull String text) { public static void setClipboard(CharSequence text) {
android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); android.content.ClipboardManager clipboard = (android.content.ClipboardManager) context
.getSystemService(Context.CLIPBOARD_SERVICE);
android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text); android.content.ClipData clip = android.content.ClipData.newPlainText("ReVanced", text);
clipboard.setPrimaryClip(clip); clipboard.setPrimaryClip(clip);
} }
@@ -548,14 +551,15 @@ public class Utils {
private static void showToast(@NonNull String messageToToast, int toastDuration) { private static void showToast(@NonNull String messageToToast, int toastDuration) {
Objects.requireNonNull(messageToToast); Objects.requireNonNull(messageToToast);
runOnMainThreadNowOrLater(() -> { runOnMainThreadNowOrLater(() -> {
if (context == null) { Context currentContext = context;
Logger.initializationException(Utils.class, "Cannot show toast (context is null): " + messageToToast, null);
} else { if (currentContext == null) {
Logger.printDebug(() -> "Showing toast: " + messageToToast); Logger.initializationException(() -> "Cannot show toast (context is null): " + messageToToast, null);
Toast.makeText(context, messageToToast, toastDuration).show(); } else {
} Logger.printDebug(() -> "Showing toast: " + messageToToast);
} Toast.makeText(currentContext, messageToToast, toastDuration).show();
); }
});
} }
public static boolean isDarkModeEnabled() { public static boolean isDarkModeEnabled() {
@@ -579,7 +583,7 @@ public class Utils {
} }
/** /**
* Automatically logs any exceptions the runnable throws * Automatically logs any exceptions the runnable throws.
*/ */
public static void runOnMainThreadDelayed(@NonNull Runnable runnable, long delayMillis) { public static void runOnMainThreadDelayed(@NonNull Runnable runnable, long delayMillis) {
Runnable loggingRunnable = () -> { Runnable loggingRunnable = () -> {
@@ -605,14 +609,14 @@ public class Utils {
} }
/** /**
* @return if the calling thread is on the main thread * @return if the calling thread is on the main thread.
*/ */
public static boolean isCurrentlyOnMainThread() { public static boolean isCurrentlyOnMainThread() {
return Looper.getMainLooper().isCurrentThread(); return Looper.getMainLooper().isCurrentThread();
} }
/** /**
* @throws IllegalStateException if the calling thread is _off_ the main thread * @throws IllegalStateException if the calling thread is _off_ the main thread.
*/ */
public static void verifyOnMainThread() throws IllegalStateException { public static void verifyOnMainThread() throws IllegalStateException {
if (!isCurrentlyOnMainThread()) { if (!isCurrentlyOnMainThread()) {
@@ -621,7 +625,7 @@ public class Utils {
} }
/** /**
* @throws IllegalStateException if the calling thread is _on_ the main thread * @throws IllegalStateException if the calling thread is _on_ the main thread.
*/ */
public static void verifyOffMainThread() throws IllegalStateException { public static void verifyOffMainThread() throws IllegalStateException {
if (isCurrentlyOnMainThread()) { if (isCurrentlyOnMainThread()) {
@@ -635,6 +639,11 @@ public class Utils {
OTHER, OTHER,
} }
/**
* Calling extension code must ensure the un-patched app has the permission
* <code>android.permission.ACCESS_NETWORK_STATE</code>, otherwise the app will crash
* if this method is used.
*/
public static boolean isNetworkConnected() { public static boolean isNetworkConnected() {
NetworkType networkType = getNetworkType(); NetworkType networkType = getNetworkType();
return networkType == NetworkType.MOBILE return networkType == NetworkType.MOBILE
@@ -642,10 +651,11 @@ public class Utils {
} }
/** /**
* Calling extension code must ensure the target app has the * Calling extension code must ensure the un-patched app has the permission
* <code>ACCESS_NETWORK_STATE</code> app manifest permission. * <code>android.permission.ACCESS_NETWORK_STATE</code>, otherwise the app will crash
* if this method is used.
*/ */
@SuppressWarnings({"deprecation", "MissingPermission"}) @SuppressLint({"MissingPermission", "deprecation"})
public static NetworkType getNetworkType() { public static NetworkType getNetworkType() {
Context networkContext = getContext(); Context networkContext = getContext();
if (networkContext == null) { if (networkContext == null) {
@@ -782,8 +792,9 @@ public class Utils {
preferences.add(new Pair<>(sortValue, preference)); preferences.add(new Pair<>(sortValue, preference));
} }
//noinspection ComparatorCombinators
Collections.sort(preferences, (pair1, pair2) Collections.sort(preferences, (pair1, pair2)
-> pair1.first.compareToIgnoreCase(pair2.first)); -> pair1.first.compareTo(pair2.first));
int index = 0; int index = 0;
for (Pair<String, Preference> pair : preferences) { for (Pair<String, Preference> pair : preferences) {

View File

@@ -70,7 +70,7 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
// Show the user the settings in JSON format. // Show the user the settings in JSON format.
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> { builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString()); Utils.setClipboard(getEditText().getText());
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> { }).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> {
importSettings(builder.getContext(), getEditText().getText().toString()); importSettings(builder.getContext(), getEditText().getText().toString());
}); });

View File

@@ -0,0 +1,113 @@
package app.revanced.extension.shared.settings.preference;
import static app.revanced.extension.shared.StringRef.str;
import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
/**
* Manages a buffer for storing debug logs from {@link Logger}.
* Stores just under 1MB of the most recent log data.
*
* All methods are thread-safe.
*/
public final class LogBufferManager {
/** Maximum byte size of all buffer entries. Must be less than Android's 1 MB Binder transaction limit. */
private static final int BUFFER_MAX_BYTES = 900_000;
/** Limit number of log lines. */
private static final int BUFFER_MAX_SIZE = 10_000;
private static final Deque<String> logBuffer = new ConcurrentLinkedDeque<>();
private static final AtomicInteger logBufferByteSize = new AtomicInteger();
/**
* Appends a log message to the internal buffer if debugging is enabled.
* The buffer is limited to approximately {@link #BUFFER_MAX_BYTES} or {@link #BUFFER_MAX_SIZE}
* to prevent excessive memory usage.
*
* @param message The log message to append.
*/
public static void appendToLogBuffer(String message) {
Objects.requireNonNull(message);
// It's very important that no Settings are used in this method,
// as this code is used when a context is not set and thus referencing
// a setting will crash the app.
logBuffer.addLast(message);
int newSize = logBufferByteSize.addAndGet(message.length());
// Remove oldest entries if over the log size limits.
while (newSize > BUFFER_MAX_BYTES || logBuffer.size() > BUFFER_MAX_SIZE) {
String removed = logBuffer.pollFirst();
if (removed == null) {
// Thread race of two different calls to this method, and the other thread won.
return;
}
newSize = logBufferByteSize.addAndGet(-removed.length());
}
}
/**
* Exports all logs from the internal buffer to the clipboard.
* Displays a toast with the result.
*/
public static void exportToClipboard() {
try {
if (!BaseSettings.DEBUG.get()) {
Utils.showToastShort(str("revanced_debug_logs_disabled"));
return;
}
if (logBuffer.isEmpty()) {
Utils.showToastShort(str("revanced_debug_logs_none_found"));
clearLogBufferData(); // Clear toast log entry that was just created.
return;
}
// Most (but not all) Android 13+ devices always show a "copied to clipboard" toast
// and there is no way to programmatically detect if a toast will show or not.
// Show a toast even if using Android 13+, but show ReVanced toast first (before copying to clipboard).
Utils.showToastShort(str("revanced_debug_logs_copied_to_clipboard"));
Utils.setClipboard(String.join("\n", logBuffer));
} catch (Exception ex) {
// Handle security exception if clipboard access is denied.
String errorMessage = String.format(str("revanced_debug_logs_failed_to_export"), ex.getMessage());
Utils.showToastLong(errorMessage);
Logger.printDebug(() -> errorMessage, ex);
}
}
private static void clearLogBufferData() {
// Cannot simply clear the log buffer because there is no
// write lock for both the deque and the atomic int.
// Instead pop off log entries and decrement the size one by one.
while (!logBuffer.isEmpty()) {
String removed = logBuffer.pollFirst();
if (removed != null) {
logBufferByteSize.addAndGet(-removed.length());
}
}
}
/**
* Clears the internal log buffer and displays a toast with the result.
*/
public static void clearLogBuffer() {
if (!BaseSettings.DEBUG.get()) {
Utils.showToastShort(str("revanced_debug_logs_disabled"));
return;
}
// Show toast before clearing, otherwise toast log will still remain.
Utils.showToastShort(str("revanced_debug_logs_clear_toast"));
clearLogBufferData();
}
}

View File

@@ -60,8 +60,9 @@ public class SortedListPreference extends ListPreference {
} }
} }
//noinspection ComparatorCombinators
Collections.sort(lastEntries, (pair1, pair2) Collections.sort(lastEntries, (pair1, pair2)
-> pair1.first.compareToIgnoreCase(pair2.first)); -> pair1.first.compareTo(pair2.first));
CharSequence[] sortedEntries = new CharSequence[entrySize]; CharSequence[] sortedEntries = new CharSequence[entrySize];
CharSequence[] sortedEntryValues = new CharSequence[entrySize]; CharSequence[] sortedEntryValues = new CharSequence[entrySize];

View File

@@ -71,9 +71,7 @@ final class PlayerRoutes {
return innerTubeBody.toString(); return innerTubeBody.toString();
} }
/** @SuppressWarnings("SameParameterValue")
* @noinspection SameParameterValue
*/
static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException { static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException {
var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route); var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route);

View File

@@ -6,11 +6,11 @@ dependencies {
android { android {
defaultConfig { defaultConfig {
minSdk = 24 minSdk = 21
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_1_8
} }
} }

View File

@@ -0,0 +1,78 @@
package app.revanced.extension.spotify.layout.hide.createbutton;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.spotify.shared.ComponentFilters.*;
@SuppressWarnings("unused")
public final class HideCreateButtonPatch {
/**
* A list of component filters that match whether a navigation bar item is the Create button.
* The main approach used is matching the resource id for the Create button title.
*/
private static final List<ComponentFilter> CREATE_BUTTON_COMPONENT_FILTERS = List.of(
new ResourceIdComponentFilter("navigationbar_musicappitems_create_title", "string"),
// Temporary fallback and fix for APKs merged with AntiSplit-M not having resources properly encoded,
// and thus getting the resource identifier for the Create button title always return 0.
// FIXME: Remove this once the above issue is no longer relevant.
new StringComponentFilter("spotify:create-menu")
);
/**
* A component filter for the old id of the resource which contained the Create button title.
* Used in older versions of the app.
*/
private static final ResourceIdComponentFilter OLD_CREATE_BUTTON_COMPONENT_FILTER =
new ResourceIdComponentFilter("bottom_navigation_bar_create_tab_title", "string");
/**
* Injection point. This method is called on every navigation bar item to check whether it is the Create button.
* If the navigation bar item is the Create button, it returns null to erase it.
* The method fingerprint used to patch ensures we can safely return null here.
*/
public static Object returnNullIfIsCreateButton(Object navigationBarItem) {
if (navigationBarItem == null) {
return null;
}
String stringifiedNavigationBarItem = navigationBarItem.toString();
for (ComponentFilter componentFilter : CREATE_BUTTON_COMPONENT_FILTERS) {
if (componentFilter.filterUnavailable()) {
Logger.printInfo(() -> "returnNullIfIsCreateButton: Filter " +
componentFilter.getFilterRepresentation() + " not available, skipping");
continue;
}
if (stringifiedNavigationBarItem.contains(componentFilter.getFilterValue())) {
Logger.printInfo(() -> "Hiding Create button because the navigation bar item " + navigationBarItem +
" matched the filter " + componentFilter.getFilterRepresentation());
return null;
}
}
return navigationBarItem;
}
/**
* Injection point. Called in older versions of the app. Returns whether the old navigation bar item is the old
* Create button.
*/
public static boolean isOldCreateButton(int oldNavigationBarItemTitleResId) {
if (OLD_CREATE_BUTTON_COMPONENT_FILTER.filterUnavailable()) {
Logger.printInfo(() -> "Skipping hiding old Create button because the resource id for " +
OLD_CREATE_BUTTON_COMPONENT_FILTER.resourceName + " is not available");
return false;
}
if (oldNavigationBarItemTitleResId == OLD_CREATE_BUTTON_COMPONENT_FILTER.getResourceId()) {
Logger.printInfo(() -> "Hiding old Create button because the navigation bar item title resource id" +
" matched " + OLD_CREATE_BUTTON_COMPONENT_FILTER.getFilterRepresentation());
return true;
}
return false;
}
}

View File

@@ -8,15 +8,54 @@ import app.revanced.extension.shared.Utils;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public final class CustomThemePatch { public final class CustomThemePatch {
private static final int BACKGROUND_COLOR = getColorFromString("@color/gray_7");
private static final int BACKGROUND_COLOR_SECONDARY = getColorFromString("@color/gray_15");
private static final int ACCENT_COLOR = getColorFromString("@color/spotify_green_157");
private static final int ACCENT_PRESSED_COLOR =
getColorFromString("@color/dark_brightaccent_background_press");
/** /**
* Injection point. * Returns an int representation of the color resource or hex code.
*/ */
public static long getThemeColor(String colorString) { private static int getColorFromString(String colorString) {
try { try {
return Utils.getColorFromString(colorString); return Utils.getColorFromString(colorString);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "Invalid custom color: " + colorString, ex); Logger.printException(() -> "Invalid color string: " + colorString, ex);
return Color.BLACK; return Color.BLACK;
} }
} }
/**
* Injection point. Returns an int representation of the replaced color from the original color.
*/
public static int replaceColor(int originalColor) {
switch (originalColor) {
// Playlist background color.
case 0xFF121212:
return BACKGROUND_COLOR;
// Share menu background color.
case 0xFF1F1F1F:
// Home category pills background color.
case 0xFF333333:
// Settings header background color.
case 0xFF282828:
// Spotify Connect device list background color.
case 0xFF2A2A2A:
return BACKGROUND_COLOR_SECONDARY;
// Some Lottie animations have a color that's slightly off due to rounding errors.
case 0xFF1ED760: case 0xFF1ED75F:
// Intermediate color used in some animations, same rounding issue.
case 0xFF1DB954: case 0xFF1CB854:
return ACCENT_COLOR;
case 0xFF1ABC54:
return ACCENT_PRESSED_COLOR;
default:
return originalColor;
}
}
} }

View File

@@ -33,10 +33,11 @@ public final class SanitizeSharingLinksPatch {
} }
} }
return builder.build().toString(); String sanitizedUrl = builder.build().toString();
Logger.printInfo(() -> "Sanitized url " + url + " to " + sanitizedUrl);
return sanitizedUrl;
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "sanitizeUrl failure", ex); Logger.printException(() -> "sanitizeUrl failure with " + url, ex);
return url; return url;
} }
} }

View File

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

View File

@@ -7,11 +7,11 @@ android {
compileSdk = 34 compileSdk = 34
defaultConfig { defaultConfig {
minSdk = 26 minSdk = 21
} }
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_1_8
} }
} }

View File

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

View File

@@ -105,6 +105,14 @@ public class ThemeHelper {
return isDarkTheme() ? getLightThemeColor() : getDarkThemeColor(); return isDarkTheme() ? getLightThemeColor() : getDarkThemeColor();
} }
public static int getDialogBackgroundColor() {
final String colorName = isDarkTheme()
? "yt_black1"
: "yt_white1";
return Utils.getColorFromString(colorName);
}
public static int getToolbarBackgroundColor() { public static int getToolbarBackgroundColor() {
final String colorName = isDarkTheme() final String colorName = isDarkTheme()
? "yt_black3" ? "yt_black3"

View File

@@ -686,7 +686,7 @@ public final class AlternativeThumbnailsPatch {
? "" : fullUrl.substring(imageExtensionEndIndex); ? "" : fullUrl.substring(imageExtensionEndIndex);
} }
/** @noinspection SameParameterValue */ @SuppressWarnings("SameParameterValue")
String createStillsUrl(@NonNull ThumbnailQuality qualityToUse, boolean includeViewTracking) { String createStillsUrl(@NonNull ThumbnailQuality qualityToUse, boolean includeViewTracking) {
// Images could be upgraded to webp if they are not already, but this fails quite often, // Images could be upgraded to webp if they are not already, but this fails quite often,
// especially for new videos uploaded in the last hour. // especially for new videos uploaded in the last hour.

View File

@@ -0,0 +1,35 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class DisableHapticFeedbackPatch {
/**
* Injection point.
*/
public static boolean disableChapterVibrate() {
return Settings.DISABLE_HAPTIC_FEEDBACK_CHAPTERS.get();
}
/**
* Injection point.
*/
public static boolean disableSeekUndoVibrate() {
return Settings.DISABLE_HAPTIC_FEEDBACK_SEEK_UNDO.get();
}
/**
* Injection point.
*/
public static boolean disablePreciseSeekingVibrate() {
return Settings.DISABLE_HAPTIC_FEEDBACK_PRECISE_SEEKING.get();
}
/**
* Injection point.
*/
public static boolean disableZoomVibrate() {
return Settings.DISABLE_HAPTIC_FEEDBACK_ZOOM.get();
}
}

View File

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

View File

@@ -152,11 +152,13 @@ public class ReturnYouTubeDislikePatch {
return original; // No need to check for Shorts in the context. return original; // No need to check for Shorts in the context.
} }
if (conversionContextString.contains("|shorts_dislike_button.eml")) { if (Utils.containsAny(conversionContextString,
"|shorts_dislike_button.eml", "|reel_dislike_button.eml")) {
return getShortsSpan(original, true); return getShortsSpan(original, true);
} }
if (conversionContextString.contains("|shorts_like_button.eml")) { if (Utils.containsAny(conversionContextString,
"|shorts_like_button.eml", "|reel_like_button.eml")) {
if (!Utils.containsNumber(original)) { if (!Utils.containsNumber(original)) {
Logger.printDebug(() -> "Replacing hidden likes count"); Logger.printDebug(() -> "Replacing hidden likes count");
return getShortsSpan(original, false); return getShortsSpan(original, false);
@@ -361,6 +363,11 @@ public class ReturnYouTubeDislikePatch {
if (videoId.equals(lastPrefetchedVideoId)) { if (videoId.equals(lastPrefetchedVideoId)) {
return; return;
} }
if (!Utils.isNetworkConnected()) {
Logger.printDebug(() -> "Cannot pre-fetch RYD, network is not connected");
lastPrefetchedVideoId = null;
return;
}
final boolean videoIdIsShort = VideoInformation.lastPlayerResponseIsShort(); final boolean videoIdIsShort = VideoInformation.lastPlayerResponseIsShort();
// Shorts shelf in home and subscription feed causes player response hook to be called, // Shorts shelf in home and subscription feed causes player response hook to be called,
@@ -415,6 +422,12 @@ public class ReturnYouTubeDislikePatch {
} }
Logger.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType); Logger.printDebug(() -> "New video id: " + videoId + " playerType: " + currentPlayerType);
if (!Utils.isNetworkConnected()) {
Logger.printDebug(() -> "Cannot fetch RYD, network is not connected");
currentVideoData = null;
return;
}
ReturnYouTubeDislike data = ReturnYouTubeDislike.getFetchForVideoId(videoId); ReturnYouTubeDislike data = ReturnYouTubeDislike.getFetchForVideoId(videoId);
// Pre-emptively set the data to short status. // Pre-emptively set the data to short status.
// Required to prevent Shorts data from being used on a minimized video in incognito mode. // Required to prevent Shorts data from being used on a minimized video in incognito mode.

View File

@@ -354,4 +354,23 @@ public final class VideoInformation {
return videoTime >= videoLength && videoLength > 0; return videoTime >= videoLength && videoLength > 0;
} }
/**
* Overrides the current playback speed.
* Rest of the implementation added by patch.
*/
public static void overridePlaybackSpeed(float speedOverride) {
Logger.printDebug(() -> "Overriding playback speed to: " + speedOverride);
}
/**
* Injection point.
*
* @param newlyLoadedPlaybackSpeed The current playback speed.
*/
public static void setPlaybackSpeed(float newlyLoadedPlaybackSpeed) {
if (playbackSpeed != newlyLoadedPlaybackSpeed) {
Logger.printDebug(() -> "Video speed changed: " + newlyLoadedPlaybackSpeed);
playbackSpeed = newlyLoadedPlaybackSpeed;
}
}
} }

View File

@@ -1,10 +0,0 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ZoomHapticsPatch {
public static boolean shouldVibrate() {
return !Settings.DISABLE_ZOOM_HAPTICS.get();
}
}

View File

@@ -7,11 +7,6 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
final class CommentsFilter extends Filter { final class CommentsFilter extends Filter {
private static final String TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH
= "|CellType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|";
private final StringFilterGroup commentComposer;
private final ByteArrayFilterGroup emojiPickerBufferGroup;
private final StringFilterGroup filterChipBar; private final StringFilterGroup filterChipBar;
private final ByteArrayFilterGroup aiCommentsSummary; private final ByteArrayFilterGroup aiCommentsSummary;
@@ -50,14 +45,9 @@ final class CommentsFilter extends Filter {
"super_thanks_button.eml" "super_thanks_button.eml"
); );
commentComposer = new StringFilterGroup( StringFilterGroup timestampButton = new StringFilterGroup(
Settings.HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS, Settings.HIDE_COMMENTS_TIMESTAMP_BUTTON,
"comment_composer.eml" "composer_timestamp_button.eml"
);
emojiPickerBufferGroup = new ByteArrayFilterGroup(
null,
"id.comment.quick_emoji.button"
); );
filterChipBar = new StringFilterGroup( filterChipBar = new StringFilterGroup(
@@ -77,7 +67,7 @@ final class CommentsFilter extends Filter {
createAShort, createAShort,
previewComment, previewComment,
thanksButton, thanksButton,
commentComposer, timestampButton,
filterChipBar filterChipBar
); );
} }
@@ -85,14 +75,6 @@ final class CommentsFilter extends Filter {
@Override @Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == commentComposer) {
// To completely hide the emoji buttons (and leave no empty space), the timestamp button is
// also hidden because the buffer is exactly the same and there's no way selectively hide.
return contentIndex == 0
&& path.endsWith(TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH)
&& emojiPickerBufferGroup.check(protobufBufferArray).isFiltered();
}
if (matchedGroup == filterChipBar) { if (matchedGroup == filterChipBar) {
return aiCommentsSummary.check(protobufBufferArray).isFiltered(); return aiCommentsSummary.check(protobufBufferArray).isFiltered();
} }

View File

@@ -10,18 +10,11 @@ import app.revanced.extension.youtube.settings.Settings;
*/ */
public final class PlaybackSpeedMenuFilterPatch extends Filter { public final class PlaybackSpeedMenuFilterPatch extends Filter {
/**
* Old litho based speed selection menu.
*/
public static volatile boolean isOldPlaybackSpeedMenuVisible;
/** /**
* 0.05x speed selection menu. * 0.05x speed selection menu.
*/ */
public static volatile boolean isPlaybackRateSelectorMenuVisible; public static volatile boolean isPlaybackRateSelectorMenuVisible;
private final StringFilterGroup oldPlaybackMenuGroup;
public PlaybackSpeedMenuFilterPatch() { public PlaybackSpeedMenuFilterPatch() {
// 0.05x litho speed menu. // 0.05x litho speed menu.
var playbackRateSelectorGroup = new StringFilterGroup( var playbackRateSelectorGroup = new StringFilterGroup(
@@ -29,22 +22,13 @@ public final class PlaybackSpeedMenuFilterPatch extends Filter {
"playback_rate_selector_menu_sheet.eml-js" "playback_rate_selector_menu_sheet.eml-js"
); );
// Old litho based speed menu. addPathCallbacks(playbackRateSelectorGroup);
oldPlaybackMenuGroup = new StringFilterGroup(
Settings.CUSTOM_SPEED_MENU,
"playback_speed_sheet_content.eml-js");
addPathCallbacks(playbackRateSelectorGroup, oldPlaybackMenuGroup);
} }
@Override @Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray, boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == oldPlaybackMenuGroup) { isPlaybackRateSelectorMenuVisible = true;
isOldPlaybackSpeedMenuVisible = true;
} else {
isPlaybackRateSelectorMenuVisible = true;
}
return false; return false;
} }

View File

@@ -143,12 +143,14 @@ public final class ShortsFilter extends Filter {
StringFilterGroup likeButton = new StringFilterGroup( StringFilterGroup likeButton = new StringFilterGroup(
Settings.HIDE_SHORTS_LIKE_BUTTON, Settings.HIDE_SHORTS_LIKE_BUTTON,
"shorts_like_button.eml" "shorts_like_button.eml",
"reel_like_button.eml"
); );
StringFilterGroup dislikeButton = new StringFilterGroup( StringFilterGroup dislikeButton = new StringFilterGroup(
Settings.HIDE_SHORTS_DISLIKE_BUTTON, Settings.HIDE_SHORTS_DISLIKE_BUTTON,
"shorts_dislike_button.eml" "shorts_dislike_button.eml",
"reel_dislike_button.eml"
); );
joinButton = new StringFilterGroup( joinButton = new StringFilterGroup(
@@ -168,12 +170,13 @@ public final class ShortsFilter extends Filter {
shortsActionBar = new StringFilterGroup( shortsActionBar = new StringFilterGroup(
null, null,
"shorts_action_bar.eml" "shorts_action_bar.eml",
"reel_action_bar.eml"
); );
actionButton = new StringFilterGroup( actionButton = new StringFilterGroup(
null, null,
// Can be simply 'button.eml' or 'shorts_video_action_button.eml' // Can be simply 'button.eml', 'shorts_video_action_button.eml' or 'reel_action_button.eml'
"button.eml" "button.eml"
); );
@@ -195,15 +198,18 @@ public final class ShortsFilter extends Filter {
videoActionButtonGroupList.addAll( videoActionButtonGroupList.addAll(
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_COMMENTS_BUTTON, Settings.HIDE_SHORTS_COMMENTS_BUTTON,
"reel_comment_button" "reel_comment_button",
"youtube_shorts_comment_outline"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_SHARE_BUTTON, Settings.HIDE_SHORTS_SHARE_BUTTON,
"reel_share_button" "reel_share_button",
"youtube_shorts_share_outline"
), ),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_REMIX_BUTTON, Settings.HIDE_SHORTS_REMIX_BUTTON,
"reel_remix_button" "reel_remix_button",
"youtube_shorts_remix_outline"
) )
); );
@@ -211,6 +217,12 @@ public final class ShortsFilter extends Filter {
// Suggested actions. // Suggested actions.
// //
suggestedActionsGroupList.addAll( suggestedActionsGroupList.addAll(
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_PREVIEW_COMMENT,
// Preview comment that can popup while a Short is playing.
// Uses no bundled icons, and instead the users profile photo is shown.
"shorts-comments-panel"
),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_SHOP_BUTTON, Settings.HIDE_SHORTS_SHOP_BUTTON,
"yt_outline_bag_" "yt_outline_bag_"
@@ -255,6 +267,10 @@ public final class ShortsFilter extends Filter {
Settings.HIDE_SHORTS_GREEN_SCREEN_BUTTON, Settings.HIDE_SHORTS_GREEN_SCREEN_BUTTON,
"greenscreen_temp" "greenscreen_temp"
), ),
new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_NEW_POSTS_BUTTON,
"yt_outline_box_pencil"
),
new ByteArrayFilterGroup( new ByteArrayFilterGroup(
Settings.HIDE_SHORTS_HASHTAG_BUTTON, Settings.HIDE_SHORTS_HASHTAG_BUTTON,
"yt_outline_hashtag_" "yt_outline_hashtag_"

View File

@@ -1,24 +1,57 @@
package app.revanced.extension.youtube.patches.playback.speed; package app.revanced.extension.youtube.patches.playback.speed;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.dipToPixels;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.icu.text.NumberFormat;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.animation.Animation;
import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent; import android.view.ViewParent;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.GridLayout;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import java.lang.ref.WeakReference;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Function;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch; import app.revanced.extension.youtube.patches.components.PlaybackSpeedMenuFilterPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class CustomPlaybackSpeedPatch { public class CustomPlaybackSpeedPatch {
/** /**
* Maximum playback speed, exclusive value. Custom speeds must be less than this value. * Maximum playback speed, inclusive. Custom speeds must be this or less.
* <p> * <p>
* Going over 8x does not increase the actual playback speed any higher, * Going over 8x does not increase the actual playback speed any higher,
* and the UI selector starts flickering and acting weird. * and the UI selector starts flickering and acting weird.
@@ -26,6 +59,11 @@ public class CustomPlaybackSpeedPatch {
*/ */
public static final float PLAYBACK_SPEED_MAXIMUM = 8; public static final float PLAYBACK_SPEED_MAXIMUM = 8;
/**
* Scale used to convert user speed to {@link android.widget.ProgressBar#setProgress(int)}.
*/
private static final float PROGRESS_BAR_VALUE_SCALE = 100;
/** /**
* Tap and hold speed. * Tap and hold speed.
*/ */
@@ -34,16 +72,28 @@ public class CustomPlaybackSpeedPatch {
/** /**
* Custom playback speeds. * Custom playback speeds.
*/ */
public static float[] customPlaybackSpeeds; public static final float[] customPlaybackSpeeds;
/** /**
* The last time the old playback menu was forcefully called. * Formats speeds to UI strings.
*/ */
private static long lastTimeOldPlaybackMenuInvoked; private static final NumberFormat speedFormatter = NumberFormat.getNumberInstance();
/**
* Weak reference to the currently open dialog.
*/
private static WeakReference<Dialog> currentDialog = new WeakReference<>(null);
/**
* Minimum and maximum custom playback speeds of {@link #customPlaybackSpeeds}.
*/
private static final float customPlaybackSpeedsMin, customPlaybackSpeedsMax;
static { static {
final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get(); // Cap at 2 decimals (rounds automatically).
speedFormatter.setMaximumFractionDigits(2);
final float holdSpeed = Settings.SPEED_TAP_AND_HOLD.get();
if (holdSpeed > 0 && holdSpeed <= PLAYBACK_SPEED_MAXIMUM) { if (holdSpeed > 0 && holdSpeed <= PLAYBACK_SPEED_MAXIMUM) {
TAP_AND_HOLD_SPEED = holdSpeed; TAP_AND_HOLD_SPEED = holdSpeed;
} else { } else {
@@ -51,7 +101,9 @@ public class CustomPlaybackSpeedPatch {
TAP_AND_HOLD_SPEED = Settings.SPEED_TAP_AND_HOLD.resetToDefault(); TAP_AND_HOLD_SPEED = Settings.SPEED_TAP_AND_HOLD.resetToDefault();
} }
loadCustomSpeeds(); customPlaybackSpeeds = loadCustomSpeeds();
customPlaybackSpeedsMin = customPlaybackSpeeds[0];
customPlaybackSpeedsMax = customPlaybackSpeeds[customPlaybackSpeeds.length - 1];
} }
/** /**
@@ -65,37 +117,41 @@ public class CustomPlaybackSpeedPatch {
Utils.showToastLong(str("revanced_custom_playback_speeds_invalid", PLAYBACK_SPEED_MAXIMUM)); Utils.showToastLong(str("revanced_custom_playback_speeds_invalid", PLAYBACK_SPEED_MAXIMUM));
} }
private static void loadCustomSpeeds() { private static float[] loadCustomSpeeds() {
try { try {
String[] speedStrings = Settings.CUSTOM_PLAYBACK_SPEEDS.get().split("\\s+"); // Automatically replace commas with periods,
// if the user added speeds in a localized format.
String[] speedStrings = Settings.CUSTOM_PLAYBACK_SPEEDS.get()
.replace(',', '.').split("\\s+");
Arrays.sort(speedStrings); Arrays.sort(speedStrings);
if (speedStrings.length == 0) { if (speedStrings.length == 0) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
customPlaybackSpeeds = new float[speedStrings.length]; float[] speeds = new float[speedStrings.length];
int i = 0; int i = 0;
for (String speedString : speedStrings) { for (String speedString : speedStrings) {
final float speedFloat = Float.parseFloat(speedString); final float speedFloat = Float.parseFloat(speedString);
if (speedFloat <= 0 || arrayContains(customPlaybackSpeeds, speedFloat)) { if (speedFloat <= 0 || arrayContains(speeds, speedFloat)) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
if (speedFloat >= PLAYBACK_SPEED_MAXIMUM) { if (speedFloat > PLAYBACK_SPEED_MAXIMUM) {
showInvalidCustomSpeedToast(); showInvalidCustomSpeedToast();
Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault(); Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault();
loadCustomSpeeds(); return loadCustomSpeeds();
return;
} }
customPlaybackSpeeds[i++] = speedFloat; speeds[i++] = speedFloat;
} }
return speeds;
} catch (Exception ex) { } catch (Exception ex) {
Logger.printInfo(() -> "parse error", ex); Logger.printInfo(() -> "Parse error", ex);
Utils.showToastLong(str("revanced_custom_playback_speeds_parse_exception")); Utils.showToastShort(str("revanced_custom_playback_speeds_parse_exception"));
Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault(); Settings.CUSTOM_PLAYBACK_SPEEDS.resetToDefault();
loadCustomSpeeds(); return loadCustomSpeeds();
} }
} }
@@ -113,38 +169,28 @@ public class CustomPlaybackSpeedPatch {
recyclerView.getViewTreeObserver().addOnDrawListener(() -> { recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
try { try {
if (PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible) { if (PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible) {
if (hideLithoMenuAndShowOldSpeedMenu(recyclerView, 5)) { if (hideLithoMenuAndShowCustomSpeedMenu(recyclerView, 5)) {
PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible = false; PlaybackSpeedMenuFilterPatch.isPlaybackRateSelectorMenuVisible = false;
} }
return;
} }
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "isPlaybackRateSelectorMenuVisible failure", ex); Logger.printException(() -> "onFlyoutMenuCreate failure", ex);
}
try {
if (PlaybackSpeedMenuFilterPatch.isOldPlaybackSpeedMenuVisible) {
if (hideLithoMenuAndShowOldSpeedMenu(recyclerView, 8)) {
PlaybackSpeedMenuFilterPatch.isOldPlaybackSpeedMenuVisible = false;
}
}
} catch (Exception ex) {
Logger.printException(() -> "isOldPlaybackSpeedMenuVisible failure", ex);
} }
}); });
} }
private static boolean hideLithoMenuAndShowOldSpeedMenu(RecyclerView recyclerView, int expectedChildCount) { @SuppressWarnings("SameParameterValue")
private static boolean hideLithoMenuAndShowCustomSpeedMenu(RecyclerView recyclerView, int expectedChildCount) {
if (recyclerView.getChildCount() == 0) { if (recyclerView.getChildCount() == 0) {
return false; return false;
} }
View firstChild = recyclerView.getChildAt(0); View firstChild = recyclerView.getChildAt(0);
if (!(firstChild instanceof ViewGroup PlaybackSpeedParentView)) { if (!(firstChild instanceof ViewGroup playbackSpeedParentView)) {
return false; return false;
} }
if (PlaybackSpeedParentView.getChildCount() != expectedChildCount) { if (playbackSpeedParentView.getChildCount() != expectedChildCount) {
return false; return false;
} }
@@ -168,23 +214,418 @@ public class CustomPlaybackSpeedPatch {
((ViewGroup) parentView3rd).setVisibility(View.GONE); ((ViewGroup) parentView3rd).setVisibility(View.GONE);
((ViewGroup) parentView4th).setVisibility(View.GONE); ((ViewGroup) parentView4th).setVisibility(View.GONE);
// Close the litho speed menu and show the old one. // Close the litho speed menu and show the modern custom speed dialog.
showOldPlaybackSpeedMenu(); showModernCustomPlaybackSpeedDialog(recyclerView.getContext());
Logger.printDebug(() -> "Modern playback speed dialog shown");
return true; return true;
} }
public static void showOldPlaybackSpeedMenu() { /**
// This method is sometimes used multiple times. * Displays a modern custom dialog for adjusting video playback speed.
// To prevent this, ignore method reuse within 1 second. * <p>
final long now = System.currentTimeMillis(); * This method creates a dialog with a slider, plus/minus buttons, and preset speed buttons
if (now - lastTimeOldPlaybackMenuInvoked < 1000) { * to allow the user to modify the video playback speed. The dialog is styled with rounded
Logger.printDebug(() -> "Ignoring call to showOldPlaybackSpeedMenu"); * corners and themed colors, positioned at the bottom of the screen. The playback speed
return; * can be adjusted in 0.05 increments using the slider or buttons, or set directly to preset
} * values. The dialog updates the displayed speed in real-time and applies changes to the
lastTimeOldPlaybackMenuInvoked = now; * video playback. The dialog is dismissed if the player enters Picture-in-Picture (PiP) mode.
Logger.printDebug(() -> "Old video quality menu shown"); */
@SuppressLint("SetTextI18n")
public static void showModernCustomPlaybackSpeedDialog(Context context) {
// Create a dialog without a theme for custom appearance.
Dialog dialog = new Dialog(context);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); // Remove default title bar.
// Rest of the implementation added by patch. // Store the dialog reference.
currentDialog = new WeakReference<>(dialog);
// Create main vertical LinearLayout for dialog content.
LinearLayout mainLayout = new LinearLayout(context);
mainLayout.setOrientation(LinearLayout.VERTICAL);
// Preset size constants.
final int dip4 = dipToPixels(4); // Height for handle bar.
final int dip5 = dipToPixels(5);
final int dip6 = dipToPixels(6); // Padding for mainLayout from bottom.
final int dip8 = dipToPixels(8); // Padding for mainLayout from left and right.
final int dip20 = dipToPixels(20);
final int dip32 = dipToPixels(32); // Height for in-rows speed buttons.
final int dip36 = dipToPixels(36); // Height for minus and plus buttons.
final int dip40 = dipToPixels(40); // Width for handle bar.
final int dip60 = dipToPixels(60); // Height for speed button container.
mainLayout.setPadding(dip5, dip8, dip5, dip8);
// Set rounded rectangle background for the main layout.
RoundRectShape roundRectShape = new RoundRectShape(
createCornerRadii(12), null, null);
ShapeDrawable background = new ShapeDrawable(roundRectShape);
background.getPaint().setColor(ThemeHelper.getDialogBackgroundColor());
mainLayout.setBackground(background);
// Add handle bar at the top.
View handleBar = new View(context);
ShapeDrawable handleBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(4), null, null));
handleBackground.getPaint().setColor(getAdjustedBackgroundColor(true));
handleBar.setBackground(handleBackground);
LinearLayout.LayoutParams handleParams = new LinearLayout.LayoutParams(
dip40, // handle bar width.
dip4 // handle bar height.
);
handleParams.gravity = Gravity.CENTER_HORIZONTAL; // Center horizontally.
handleParams.setMargins(0, 0, 0, dip20); // 20dp bottom margins.
handleBar.setLayoutParams(handleParams);
// Add handle bar view to main layout.
mainLayout.addView(handleBar);
// Display current playback speed.
TextView currentSpeedText = new TextView(context);
float currentSpeed = VideoInformation.getPlaybackSpeed();
// Initially show with only 0 minimum digits, so 1.0 shows as 1x
currentSpeedText.setText(formatSpeedStringX(currentSpeed, 0));
currentSpeedText.setTextColor(ThemeHelper.getForegroundColor());
currentSpeedText.setTextSize(16);
currentSpeedText.setTypeface(Typeface.DEFAULT_BOLD);
currentSpeedText.setGravity(Gravity.CENTER);
LinearLayout.LayoutParams textParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
textParams.setMargins(0, 0, 0, 0);
currentSpeedText.setLayoutParams(textParams);
// Add current speed text view to main layout.
mainLayout.addView(currentSpeedText);
// Create horizontal layout for slider and +/- buttons.
LinearLayout sliderLayout = new LinearLayout(context);
sliderLayout.setOrientation(LinearLayout.HORIZONTAL);
sliderLayout.setGravity(Gravity.CENTER_VERTICAL);
sliderLayout.setPadding(dip5, dip5, dip5, dip5); // 5dp padding.
// Create minus button.
Button minusButton = new Button(context, null, 0); // Disable default theme style.
minusButton.setText(""); // No text on button.
ShapeDrawable minusBackground = new ShapeDrawable(new RoundRectShape(createCornerRadii(20), null, null));
minusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
minusButton.setBackground(minusBackground);
OutlineSymbolDrawable minusDrawable = new OutlineSymbolDrawable(false); // Minus symbol.
minusButton.setForeground(minusDrawable);
LinearLayout.LayoutParams minusParams = new LinearLayout.LayoutParams(dip36, dip36);
minusParams.setMargins(0, 0, dip5, 0); // 5dp to slider.
minusButton.setLayoutParams(minusParams);
// Create slider for speed adjustment.
SeekBar speedSlider = new SeekBar(context);
speedSlider.setMax(speedToProgressValue(customPlaybackSpeedsMax));
speedSlider.setProgress(speedToProgressValue(currentSpeed));
speedSlider.getProgressDrawable().setColorFilter(
ThemeHelper.getForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme progress bar.
speedSlider.getThumb().setColorFilter(
ThemeHelper.getForegroundColor(), PorterDuff.Mode.SRC_IN); // Theme slider thumb.
LinearLayout.LayoutParams sliderParams = new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f);
sliderParams.setMargins(dip5, 0, dip5, 0); // 5dp to -/+ buttons.
speedSlider.setLayoutParams(sliderParams);
// Create plus button.
Button plusButton = new Button(context, null, 0); // Disable default theme style.
plusButton.setText(""); // No text on button.
ShapeDrawable plusBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(20), null, null));
plusBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
plusButton.setBackground(plusBackground);
OutlineSymbolDrawable plusDrawable = new OutlineSymbolDrawable(true); // Plus symbol.
plusButton.setForeground(plusDrawable);
LinearLayout.LayoutParams plusParams = new LinearLayout.LayoutParams(dip36, dip36);
plusParams.setMargins(dip5, 0, 0, 0); // 5dp to slider.
plusButton.setLayoutParams(plusParams);
// Add -/+ and slider views to slider layout.
sliderLayout.addView(minusButton);
sliderLayout.addView(speedSlider);
sliderLayout.addView(plusButton);
LinearLayout.LayoutParams sliderLayoutParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
sliderLayoutParams.setMargins(0, 0, 0, dip5); // 5dp bottom margin.
sliderLayout.setLayoutParams(sliderLayoutParams);
// Add slider layout to main layout.
mainLayout.addView(sliderLayout);
Function<Float, Void> userSelectedSpeed = newSpeed -> {
final float roundedSpeed = roundSpeedToNearestIncrement(newSpeed);
if (VideoInformation.getPlaybackSpeed() == roundedSpeed) {
// Nothing has changed. New speed rounds to the current speed.
return null;
}
VideoInformation.overridePlaybackSpeed(roundedSpeed);
RememberPlaybackSpeedPatch.userSelectedPlaybackSpeed(roundedSpeed);
currentSpeedText.setText(formatSpeedStringX(roundedSpeed, 2)); // Update display.
speedSlider.setProgress(speedToProgressValue(roundedSpeed)); // Update slider.
return null;
};
// Set listener for slider to update playback speed.
speedSlider.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
// Convert from progress value to video playback speed.
userSelectedSpeed.apply(customPlaybackSpeedsMin + (progress / PROGRESS_BAR_VALUE_SCALE));
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
});
minusButton.setOnClickListener(v -> userSelectedSpeed.apply(
VideoInformation.getPlaybackSpeed() - 0.05f));
plusButton.setOnClickListener(v -> userSelectedSpeed.apply(
VideoInformation.getPlaybackSpeed() + 0.05f));
// Create GridLayout for preset speed buttons.
GridLayout gridLayout = new GridLayout(context);
gridLayout.setColumnCount(5); // 5 columns for speed buttons.
gridLayout.setAlignmentMode(GridLayout.ALIGN_BOUNDS);
gridLayout.setRowCount((int) Math.ceil(customPlaybackSpeeds.length / 5.0));
LinearLayout.LayoutParams gridParams = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
gridParams.setMargins(0, 0, 0, 0); // No margins around GridLayout.
gridLayout.setLayoutParams(gridParams);
// For all buttons show at least 1 zero in decimal (2 -> "2.0").
speedFormatter.setMinimumFractionDigits(1);
// Add buttons for each preset playback speed.
for (float speed : customPlaybackSpeeds) {
// Container for button and optional label.
FrameLayout buttonContainer = new FrameLayout(context);
// Set layout parameters for each grid cell.
GridLayout.LayoutParams containerParams = new GridLayout.LayoutParams();
containerParams.width = 0; // Equal width for columns.
containerParams.columnSpec = GridLayout.spec(GridLayout.UNDEFINED, 1, 1f);
containerParams.setMargins(dip5, 0, dip5, 0); // Button margins.
containerParams.height = dip60; // Fixed height for button and label.
buttonContainer.setLayoutParams(containerParams);
// Create speed button.
Button speedButton = new Button(context, null, 0);
speedButton.setText(speedFormatter.format(speed)); // Do not use 'x' speed format.
speedButton.setTextColor(ThemeHelper.getForegroundColor());
speedButton.setTextSize(12);
speedButton.setAllCaps(false);
speedButton.setGravity(Gravity.CENTER);
ShapeDrawable buttonBackground = new ShapeDrawable(new RoundRectShape(
createCornerRadii(20), null, null));
buttonBackground.getPaint().setColor(getAdjustedBackgroundColor(false));
speedButton.setBackground(buttonBackground);
speedButton.setPadding(dip5, dip5, dip5, dip5);
// Center button vertically and stretch horizontally in container.
FrameLayout.LayoutParams buttonParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, dip32, Gravity.CENTER);
speedButton.setLayoutParams(buttonParams);
// Add speed buttons view to buttons container layout.
buttonContainer.addView(speedButton);
// Add "Normal" label for 1.0x speed.
if (speed == 1.0f) {
TextView normalLabel = new TextView(context);
// Use same 'Normal' string as stock YouTube.
normalLabel.setText(str("normal_playback_rate_label"));
normalLabel.setTextColor(ThemeHelper.getForegroundColor());
normalLabel.setTextSize(10);
normalLabel.setGravity(Gravity.CENTER);
FrameLayout.LayoutParams labelParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
labelParams.bottomMargin = 0; // Position label below button.
normalLabel.setLayoutParams(labelParams);
buttonContainer.addView(normalLabel);
}
speedButton.setOnClickListener(v -> userSelectedSpeed.apply(speed));
gridLayout.addView(buttonContainer);
}
// Add in-rows speed buttons layout to main layout.
mainLayout.addView(gridLayout);
// Wrap mainLayout in another LinearLayout for side margins.
LinearLayout wrapperLayout = new LinearLayout(context);
wrapperLayout.setOrientation(LinearLayout.VERTICAL);
wrapperLayout.setPadding(dip8, 0, dip8, 0); // 8dp side margins.
wrapperLayout.addView(mainLayout);
dialog.setContentView(wrapperLayout);
// Configure dialog window to appear at the bottom.
Window window = dialog.getWindow();
if (window != null) {
WindowManager.LayoutParams params = window.getAttributes();
params.gravity = Gravity.BOTTOM; // Position at bottom of screen.
params.y = dip6; // 6dp margin from bottom.
// In landscape, use the smaller dimension (height) as portrait width.
int portraitWidth = context.getResources().getDisplayMetrics().widthPixels;
if (context.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
portraitWidth = Math.min(
portraitWidth,
context.getResources().getDisplayMetrics().heightPixels);
}
params.width = portraitWidth; // Use portrait width.
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
window.setAttributes(params);
window.setBackgroundDrawable(null); // Remove default dialog background.
}
// Create observer for PlayerType changes.
Function1<PlayerType, Unit> playerTypeObserver = new Function1<>() {
@Override
public Unit invoke(PlayerType type) {
Dialog current = currentDialog.get();
if (current == null || !current.isShowing()) {
// Should never happen.
PlayerType.getOnChange().removeObserver(this);
Logger.printException(() -> "Removing player type listener as dialog is null or closed");
} else if (type == PlayerType.WATCH_WHILE_PICTURE_IN_PICTURE) {
current.dismiss();
Logger.printDebug(() -> "Playback speed dialog dismissed due to PiP mode");
}
return Unit.INSTANCE;
}
};
// Add observer to dismiss dialog when entering PiP mode.
PlayerType.getOnChange().addObserver(playerTypeObserver);
// Remove observer when dialog is dismissed.
dialog.setOnDismissListener(d -> {
PlayerType.getOnChange().removeObserver(playerTypeObserver);
Logger.printDebug(() -> "PlayerType observer removed on dialog dismiss");
});
// Apply slide-in animation when showing the dialog.
final int fadeDurationFast = Utils.getResourceInteger("fade_duration_fast");
Animation slideInABottomAnimation = Utils.getResourceAnimation("slide_in_bottom");
slideInABottomAnimation.setDuration(fadeDurationFast);
mainLayout.startAnimation(slideInABottomAnimation);
dialog.show(); // Display the dialog.
}
/**
* Creates an array of corner radii for a rounded rectangle shape.
*
* @param dp The radius in density-independent pixels (dp) to apply to all corners.
* @return An array of eight float values representing the corner radii
* (top-left, top-right, bottom-right, bottom-left).
*/
private static float[] createCornerRadii(float dp) {
final float radius = dipToPixels(dp);
return new float[]{radius, radius, radius, radius, radius, radius, radius, radius};
}
/**
* @param speed The playback speed value to format.
* @return A string representation of the speed with 'x' (e.g. "1.25x" or "1.00x").
*/
private static String formatSpeedStringX(float speed, int minimumFractionDigits) {
speedFormatter.setMinimumFractionDigits(minimumFractionDigits);
return speedFormatter.format(speed) + 'x';
}
/**
* @return user speed converted to a value for {@link SeekBar#setProgress(int)}.
*/
private static int speedToProgressValue(float speed) {
return (int) ((speed - customPlaybackSpeedsMin) * PROGRESS_BAR_VALUE_SCALE);
}
/**
* Rounds the given playback speed to the nearest 0.05 increment and ensures it is within valid bounds.
*
* @param speed The playback speed to round.
* @return The rounded speed, constrained to the specified bounds.
*/
private static float roundSpeedToNearestIncrement(float speed) {
// Round to nearest 0.05 speed.
final float roundedSpeed = Math.round(speed / 0.05f) * 0.05f;
return Utils.clamp(roundedSpeed, 0.05f, PLAYBACK_SPEED_MAXIMUM);
}
/**
* Adjusts the background color based on the current theme.
*
* @param isHandleBar If true, applies a stronger darkening factor (0.9) for the handle bar in light theme;
* if false, applies a standard darkening factor (0.95) for other elements in light theme.
* @return A modified background color, lightened by 20% for dark themes or darkened by 5% (or 10% for handle bar)
* for light themes to ensure visual contrast.
*/
public static int getAdjustedBackgroundColor(boolean isHandleBar) {
final int baseColor = ThemeHelper.getDialogBackgroundColor();
float darkThemeFactor = isHandleBar ? 1.25f : 1.115f; // 1.25f for handleBar, 1.115f for others in dark theme.
float lightThemeFactor = isHandleBar ? 0.9f : 0.95f; // 0.9f for handleBar, 0.95f for others in light theme.
return ThemeHelper.isDarkTheme()
? ThemeHelper.adjustColorBrightness(baseColor, darkThemeFactor) // Lighten for dark theme.
: ThemeHelper.adjustColorBrightness(baseColor, lightThemeFactor); // Darken for light theme.
}
}
/**
* Custom Drawable for rendering outlined plus and minus symbols on buttons.
*/
class OutlineSymbolDrawable extends Drawable {
private final boolean isPlus; // Determines if the symbol is a plus or minus.
private final Paint paint;
OutlineSymbolDrawable(boolean isPlus) {
this.isPlus = isPlus;
paint = new Paint(Paint.ANTI_ALIAS_FLAG); // Enable anti-aliasing for smooth rendering.
paint.setColor(ThemeHelper.getForegroundColor());
paint.setStyle(Paint.Style.STROKE); // Use stroke style for outline.
paint.setStrokeWidth(dipToPixels(1)); // 1dp stroke width.
}
@Override
public void draw(Canvas canvas) {
Rect bounds = getBounds();
final int width = bounds.width();
final int height = bounds.height();
final float centerX = width / 2f; // Center X coordinate.
final float centerY = height / 2f; // Center Y coordinate.
final float size = Math.min(width, height) * 0.25f; // Symbol size is 25% of button dimensions.
// Draw horizontal line for both plus and minus symbols.
canvas.drawLine(centerX - size, centerY, centerX + size, centerY, paint);
if (isPlus) {
// Draw vertical line for plus symbol.
canvas.drawLine(centerX, centerY - size, centerX, centerY + size, paint);
}
}
@Override
public void setAlpha(int alpha) {
paint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
paint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
} }
} }

View File

@@ -33,10 +33,10 @@ public final class RememberPlaybackSpeedPatch {
public static void userSelectedPlaybackSpeed(float playbackSpeed) { public static void userSelectedPlaybackSpeed(float playbackSpeed) {
try { try {
if (Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get()) { if (Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get()) {
// With the 0.05x menu, if the speed is set by integrations to higher than 2.0x // With the 0.05x menu, if the speed is set by a patch to higher than 2.0x
// then the menu will allow increasing without bounds but the max speed is // then the menu will allow increasing without bounds but the max speed is
// still capped to under 8.0x. // still capped to 8.0x.
playbackSpeed = Math.min(playbackSpeed, CustomPlaybackSpeedPatch.PLAYBACK_SPEED_MAXIMUM - 0.05f); playbackSpeed = Math.min(playbackSpeed, CustomPlaybackSpeedPatch.PLAYBACK_SPEED_MAXIMUM);
// Prevent toast spamming if using the 0.05x adjustments. // Prevent toast spamming if using the 0.05x adjustments.
// Show exactly one toast after the user stops interacting with the speed menu. // Show exactly one toast after the user stops interacting with the speed menu.
@@ -57,7 +57,7 @@ public final class RememberPlaybackSpeedPatch {
} }
Settings.PLAYBACK_SPEED_DEFAULT.save(finalPlaybackSpeed); Settings.PLAYBACK_SPEED_DEFAULT.save(finalPlaybackSpeed);
Utils.showToastLong(str("revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x"))); Utils.showToastShort(str("revanced_remember_playback_speed_toast", (finalPlaybackSpeed + "x")));
}, TOAST_DELAY_MILLISECONDS); }, TOAST_DELAY_MILLISECONDS);
} }
} catch (Exception ex) { } catch (Exception ex) {

View File

@@ -2,6 +2,7 @@ package app.revanced.extension.youtube.patches.theme;
import static app.revanced.extension.shared.StringRef.str; import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.clamp; import static app.revanced.extension.shared.Utils.clamp;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Color; import android.graphics.Color;
@@ -60,7 +61,7 @@ public final class SeekbarColorPatch {
* this is the color value of {@link Settings#SEEKBAR_CUSTOM_COLOR_PRIMARY}. * this is the color value of {@link Settings#SEEKBAR_CUSTOM_COLOR_PRIMARY}.
* Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}. * Otherwise this is {@link #ORIGINAL_SEEKBAR_COLOR}.
*/ */
private static int customSeekbarColor = ORIGINAL_SEEKBAR_COLOR; private static final int customSeekbarColor;
/** /**
* Custom seekbar hue, saturation, and brightness values. * Custom seekbar hue, saturation, and brightness values.
@@ -77,24 +78,25 @@ public final class SeekbarColorPatch {
Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv); Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv);
ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS = hsv[2]; ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS = hsv[2];
if (SEEKBAR_CUSTOM_COLOR_ENABLED) { customSeekbarColor = SEEKBAR_CUSTOM_COLOR_ENABLED
loadCustomSeekbarColor(); ? loadCustomSeekbarColor()
} : ORIGINAL_SEEKBAR_COLOR;
} }
private static void loadCustomSeekbarColor() { private static int loadCustomSeekbarColor() {
try { try {
customSeekbarColor = Color.parseColor(Settings.SEEKBAR_CUSTOM_COLOR_PRIMARY.get()); final int color = Color.parseColor(Settings.SEEKBAR_CUSTOM_COLOR_PRIMARY.get());
Color.colorToHSV(customSeekbarColor, customSeekbarColorHSV); Color.colorToHSV(color, customSeekbarColorHSV);
customSeekbarColorGradient[0] = color;
customSeekbarColorGradient[0] = customSeekbarColor;
customSeekbarColorGradient[1] = Color.parseColor(Settings.SEEKBAR_CUSTOM_COLOR_ACCENT.get()); customSeekbarColorGradient[1] = Color.parseColor(Settings.SEEKBAR_CUSTOM_COLOR_ACCENT.get());
return color;
} catch (Exception ex) { } catch (Exception ex) {
Utils.showToastShort(str("revanced_seekbar_custom_color_invalid")); Utils.showToastShort(str("revanced_seekbar_custom_color_invalid"));
Settings.SEEKBAR_CUSTOM_COLOR_PRIMARY.resetToDefault(); Settings.SEEKBAR_CUSTOM_COLOR_PRIMARY.resetToDefault();
Settings.SEEKBAR_CUSTOM_COLOR_ACCENT.resetToDefault(); Settings.SEEKBAR_CUSTOM_COLOR_ACCENT.resetToDefault();
loadCustomSeekbarColor(); return loadCustomSeekbarColor();
} }
} }
@@ -114,6 +116,7 @@ public final class SeekbarColorPatch {
: (int) channel3Bits; : (int) channel3Bits;
} }
@SuppressWarnings("SameParameterValue")
private static String get9BitStyleIdentifier(int color24Bit) { private static String get9BitStyleIdentifier(int color24Bit) {
final int r3 = colorChannelTo3Bits(Color.red(color24Bit)); final int r3 = colorChannelTo3Bits(Color.red(color24Bit));
final int g3 = colorChannelTo3Bits(Color.green(color24Bit)); final int g3 = colorChannelTo3Bits(Color.green(color24Bit));
@@ -171,23 +174,15 @@ public final class SeekbarColorPatch {
*/ */
public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) { public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) {
try { try {
if (!SEEKBAR_CUSTOM_COLOR_ENABLED) { SplashScreenAnimationStyle animationStyle = Settings.SPLASH_SCREEN_ANIMATION_STYLE.get();
if (!SEEKBAR_CUSTOM_COLOR_ENABLED
// Black and white animations cannot use color replacements.
|| animationStyle == SplashScreenAnimationStyle.FPS_30_BLACK_AND_WHITE
|| animationStyle == SplashScreenAnimationStyle.FPS_60_BLACK_AND_WHITE) {
view.patch_setAnimation(resourceId); view.patch_setAnimation(resourceId);
return; return;
} }
//noinspection ConstantConditions
if (false) { // Set true to force slow animation for development.
final int longAnimation = Utils.getResourceIdentifier(
Utils.isDarkModeEnabled()
? "startup_animation_5s_30fps_dark"
: "startup_animation_5s_30fps_light",
"raw");
if (longAnimation != 0) {
resourceId = longAnimation;
}
}
// Must specify primary key name otherwise the morphing YT logo color is also changed. // Must specify primary key name otherwise the morphing YT logo color is also changed.
String originalKey = "\"k\":"; String originalKey = "\"k\":";
String originalPrimary = originalKey + "[1,0,0.2,1]"; String originalPrimary = originalKey + "[1,0,0.2,1]";
@@ -197,21 +192,16 @@ public final class SeekbarColorPatch {
String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]); String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]);
String json = loadRawResourceAsString(resourceId); String json = loadRawResourceAsString(resourceId);
if (json == null) { String replacement = json
return; // Should never happen. .replace(originalPrimary, replacementPrimary)
} .replace(originalAccent, replacementAccent);
if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) { if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) {
String jsonFinal = json; Logger.printException(() -> "Could not replace splash animation colors: " + json);
Logger.printException(() -> "Could not replace launch animation colors: " + jsonFinal);
} }
Logger.printDebug(() -> "Replacing Lottie animation JSON");
json = json.replace(originalPrimary, replacementPrimary);
json = json.replace(originalAccent, replacementAccent);
// cacheKey is not needed since the animation will not be reused. // cacheKey is not needed since the animation will not be reused.
view.patch_setAnimation(new ByteArrayInputStream(json.getBytes()), null); view.patch_setAnimation(new ByteArrayInputStream(replacement.getBytes()), null);
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "setSplashAnimationLottie failure", ex); Logger.printException(() -> "setSplashAnimationLottie failure", ex);
} }
@@ -232,8 +222,7 @@ public final class SeekbarColorPatch {
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) { Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
return scanner.next(); return scanner.next();
} catch (IOException e) { } catch (IOException e) {
Logger.printException(() -> "Could not load resource: " + resourceId); throw new IllegalStateException("Could not load resource: " + resourceId);
return null;
} }
} }

View File

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

View File

@@ -21,14 +21,12 @@ import static app.revanced.extension.youtube.patches.MiniplayerPatch.MiniplayerT
import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType; import static app.revanced.extension.youtube.patches.OpenShortsInRegularPlayerPatch.ShortsPlayerType;
import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability; import static app.revanced.extension.youtube.patches.SeekbarThumbnailsPatch.SeekbarThumbnailsHighQualityAvailability;
import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability; import static app.revanced.extension.youtube.patches.components.PlayerFlyoutMenuItemsFilter.HideAudioFlyoutMenuAvailability;
import static app.revanced.extension.youtube.patches.theme.ThemePatch.SplashScreenAnimationStyle;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.IGNORE;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.MANUAL_SKIP;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY;
import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE; import static app.revanced.extension.youtube.sponsorblock.objects.CategoryBehaviour.SKIP_AUTOMATICALLY_ONCE;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
import android.graphics.Color; import android.graphics.Color;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
@@ -40,12 +38,14 @@ import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.shared.settings.LongSetting; import app.revanced.extension.shared.settings.LongSetting;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.settings.StringSetting; import app.revanced.extension.shared.settings.StringSetting;
import app.revanced.extension.shared.settings.preference.SharedPrefCategory;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime; import app.revanced.extension.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
import app.revanced.extension.youtube.patches.MiniplayerPatch; import app.revanced.extension.youtube.patches.MiniplayerPatch;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
public class Settings extends BaseSettings { public class Settings extends BaseSettings {
// Video // Video
@@ -63,7 +63,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE); public static final BooleanSetting CUSTOM_SPEED_MENU = new BooleanSetting("revanced_custom_speed_menu", TRUE);
public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f); public static final FloatSetting PLAYBACK_SPEED_DEFAULT = new FloatSetting("revanced_playback_speed_default", -2.0f);
public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds", public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds",
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true); "0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.5\n3.0\n4.0\n5.0\n6.0\n7.0\n8.0", true);
// Audio // Audio
public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability()); public static final BooleanSetting FORCE_ORIGINAL_AUDIO = new BooleanSetting("revanced_force_original_audio", FALSE, new ForceOriginalAudioAvailability());
@@ -135,6 +135,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true); public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE); public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true); public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
public static final BooleanSetting HIDE_PLAYER_CONTROL_BUTTONS_BACKGROUND = new BooleanSetting("revanced_hide_player_control_buttons_background", FALSE, true);
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE); public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
public static final BooleanSetting HIDE_CHANNEL_MEMBER_SHELF = new BooleanSetting("revanced_hide_channel_member_shelf", TRUE); public static final BooleanSetting HIDE_CHANNEL_MEMBER_SHELF = new BooleanSetting("revanced_hide_channel_member_shelf", TRUE);
public static final BooleanSetting HIDE_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_community_guidelines", TRUE); public static final BooleanSetting HIDE_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_community_guidelines", TRUE);
@@ -179,7 +180,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_COMMENTS_AI_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_summary", FALSE); public static final BooleanSetting HIDE_COMMENTS_AI_SUMMARY = new BooleanSetting("revanced_hide_comments_ai_summary", FALSE);
public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE); public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE);
public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE); public static final BooleanSetting HIDE_COMMENTS_CREATE_A_SHORT_BUTTON = new BooleanSetting("revanced_hide_comments_create_a_short_button", TRUE);
public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comments_timestamp_and_emoji_buttons", TRUE); public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_BUTTON = new BooleanSetting("revanced_hide_comments_timestamp_button", FALSE);
public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE); public static final BooleanSetting HIDE_COMMENTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_comments_preview_comment", FALSE);
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE); public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE);
public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE); public static final BooleanSetting HIDE_COMMENTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
@@ -226,6 +227,8 @@ public class Settings extends BaseSettings {
public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message"); public static final EnumSetting<FormFactor> CHANGE_FORM_FACTOR = new EnumSetting<>("revanced_change_form_factor", FormFactor.DEFAULT, true, "revanced_change_form_factor_user_dialog_message");
public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true); public static final BooleanSetting BYPASS_IMAGE_REGION_RESTRICTIONS = new BooleanSetting("revanced_bypass_image_region_restrictions", FALSE, true);
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true); public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE, true);
public static final EnumSetting<SplashScreenAnimationStyle> SPLASH_SCREEN_ANIMATION_STYLE = new EnumSetting<>("splash_screen_animation_style", SplashScreenAnimationStyle.FPS_60_ONE_SECOND, true);
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE, public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
"revanced_remove_viewer_discretion_dialog_user_dialog_message"); "revanced_remove_viewer_discretion_dialog_user_dialog_message");
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message"); public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
@@ -260,6 +263,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE); public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE); public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE);
public static final BooleanSetting HIDE_SHORTS_GREEN_SCREEN_BUTTON = new BooleanSetting("revanced_hide_shorts_green_screen_button", TRUE); public static final BooleanSetting HIDE_SHORTS_GREEN_SCREEN_BUTTON = new BooleanSetting("revanced_hide_shorts_green_screen_button", TRUE);
public static final BooleanSetting HIDE_SHORTS_NEW_POSTS_BUTTON = new BooleanSetting("revanced_hide_shorts_new_posts_button", TRUE);
public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE); public static final BooleanSetting HIDE_SHORTS_HASHTAG_BUTTON = new BooleanSetting("revanced_hide_shorts_hashtag_button", TRUE);
public static final BooleanSetting HIDE_SHORTS_HISTORY = new BooleanSetting("revanced_hide_shorts_history", FALSE); public static final BooleanSetting HIDE_SHORTS_HISTORY = new BooleanSetting("revanced_hide_shorts_history", FALSE);
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE); public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
@@ -275,6 +279,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE); public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE);
public static final BooleanSetting HIDE_SHORTS_SEARCH_SUGGESTIONS = new BooleanSetting("revanced_hide_shorts_search_suggestions", TRUE); public static final BooleanSetting HIDE_SHORTS_SEARCH_SUGGESTIONS = new BooleanSetting("revanced_hide_shorts_search_suggestions", TRUE);
public static final BooleanSetting HIDE_SHORTS_SHARE_BUTTON = new BooleanSetting("revanced_hide_shorts_share_button", FALSE); public static final BooleanSetting HIDE_SHORTS_SHARE_BUTTON = new BooleanSetting("revanced_hide_shorts_share_button", FALSE);
public static final BooleanSetting HIDE_SHORTS_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_shorts_preview_comment", TRUE);
public static final BooleanSetting HIDE_SHORTS_SHOP_BUTTON = new BooleanSetting("revanced_hide_shorts_shop_button", TRUE); public static final BooleanSetting HIDE_SHORTS_SHOP_BUTTON = new BooleanSetting("revanced_hide_shorts_shop_button", TRUE);
public static final BooleanSetting HIDE_SHORTS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", FALSE); public static final BooleanSetting HIDE_SHORTS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", FALSE);
public static final BooleanSetting HIDE_SHORTS_SOUND_METADATA_LABEL = new BooleanSetting("revanced_hide_shorts_sound_metadata_label", FALSE); public static final BooleanSetting HIDE_SHORTS_SOUND_METADATA_LABEL = new BooleanSetting("revanced_hide_shorts_sound_metadata_label", FALSE);
@@ -309,16 +314,16 @@ public class Settings extends BaseSettings {
public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE); public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE);
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE); public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false); public static final BooleanSetting CHECK_WATCH_HISTORY_DOMAIN_NAME = new BooleanSetting("revanced_check_watch_history_domain_name", TRUE, false, false);
public static final BooleanSetting DISABLE_ZOOM_HAPTICS = new BooleanSetting("revanced_disable_zoom_haptics", 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);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_SEEK_UNDO = new BooleanSetting("revanced_disable_haptic_feedback_seek_undo", FALSE);
public static final BooleanSetting DISABLE_HAPTIC_FEEDBACK_ZOOM = new BooleanSetting("revanced_disable_haptic_feedback_zoom", FALSE);
public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true); public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true);
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE); public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true, public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
"revanced_spoof_device_dimensions_user_dialog_message"); "revanced_spoof_device_dimensions_user_dialog_message");
/** public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false,
* When enabled, share the debug logs with care. "revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG));
* The buffer contains select user data, including the client ip address and information that could identify the end user.
*/
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
// Swipe controls // Swipe controls
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true); public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true);
@@ -337,13 +342,17 @@ public class Settings extends BaseSettings {
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true, public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final StringSetting SWIPE_OVERLAY_PROGRESS_COLOR = new StringSetting("revanced_swipe_overlay_progress_color", "#FFFFFF", true, public static final StringSetting SWIPE_OVERLAY_BRIGHTNESS_COLOR = new StringSetting("revanced_swipe_overlay_progress_brightness_color", "#FFFFFF", true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parent(SWIPE_BRIGHTNESS));
public static final StringSetting SWIPE_OVERLAY_VOLUME_COLOR = new StringSetting("revanced_swipe_overlay_progress_volume_color", "#FFFFFF", true,
parent(SWIPE_VOLUME));
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true, public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true, parent(SWIPE_BRIGHTNESS)); public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true,
parent(SWIPE_BRIGHTNESS));
public static final FloatSetting SWIPE_BRIGHTNESS_VALUE = new FloatSetting("revanced_swipe_brightness_value", -1f); public static final FloatSetting SWIPE_BRIGHTNESS_VALUE = new FloatSetting("revanced_swipe_brightness_value", -1f);
public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true, parent(SWIPE_BRIGHTNESS)); public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true,
parent(SWIPE_BRIGHTNESS));
// ReturnYoutubeDislike // ReturnYoutubeDislike
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE); public static final BooleanSetting RYD_ENABLED = new BooleanSetting("revanced_ryd_enabled", TRUE);

View File

@@ -0,0 +1,34 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* A custom preference that clears the ReVanced debug log buffer when clicked.
* Invokes the {@link LogBufferManager#clearLogBuffer} method.
*/
@SuppressWarnings("unused")
public class ClearLogBufferPreference extends Preference {
{
setOnPreferenceClickListener(pref -> {
LogBufferManager.clearLogBuffer();
return true;
});
}
public ClearLogBufferPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ClearLogBufferPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ClearLogBufferPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ClearLogBufferPreference(Context context) {
super(context);
}
}

View File

@@ -0,0 +1,34 @@
package app.revanced.extension.youtube.settings.preference;
import android.content.Context;
import android.util.AttributeSet;
import android.preference.Preference;
import app.revanced.extension.shared.settings.preference.LogBufferManager;
/**
* A custom preference that triggers exporting ReVanced debug logs to the clipboard when clicked.
* Invokes the {@link LogBufferManager#exportToClipboard} method.
*/
@SuppressWarnings({"deprecation", "unused"})
public class ExportLogToClipboardPreference extends Preference {
{
setOnPreferenceClickListener(pref -> {
LogBufferManager.exportToClipboard();
return true;
});
}
public ExportLogToClipboardPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ExportLogToClipboardPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ExportLogToClipboardPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExportLogToClipboardPreference(Context context) {
super(context);
}
}

View File

@@ -376,7 +376,11 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
Utils.setEditTextDialogTheme(builder); Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> { builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString()); try {
Utils.setClipboard(getEditText().getText());
} catch (Exception ex) {
Logger.printException(() -> "Copy settings failure", ex);
}
}); });
} }
}; };
@@ -433,7 +437,11 @@ public class SponsorBlockPreferenceGroup extends PreferenceGroup {
Utils.setEditTextDialogTheme(builder); Utils.setEditTextDialogTheme(builder);
builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> { builder.setNeutralButton(str("revanced_sb_settings_copy"), (dialog, which) -> {
Utils.setClipboard(getEditText().getText().toString()); try {
Utils.setClipboard(getEditText().getText());
} catch (Exception ex) {
Logger.printException(() -> "Copy settings failure", ex);
}
}); });
} }
}; };

View File

@@ -1,10 +1,10 @@
package app.revanced.extension.youtube.swipecontrols package app.revanced.extension.youtube.swipecontrols
import android.annotation.SuppressLint
import android.graphics.Color import android.graphics.Color
import app.revanced.extension.shared.Logger import app.revanced.extension.shared.Logger
import app.revanced.extension.shared.StringRef.str import app.revanced.extension.shared.StringRef.str
import app.revanced.extension.shared.Utils import app.revanced.extension.shared.Utils
import app.revanced.extension.shared.settings.StringSetting
import app.revanced.extension.youtube.settings.Settings import app.revanced.extension.youtube.settings.Settings
import app.revanced.extension.youtube.shared.PlayerType import app.revanced.extension.youtube.shared.PlayerType
@@ -51,105 +51,112 @@ class SwipeControlsConfigurationProvider {
/** /**
* Indicates whether press-to-swipe mode is enabled, requiring a press before swiping to activate controls. * Indicates whether press-to-swipe mode is enabled, requiring a press before swiping to activate controls.
*/ */
val shouldEnablePressToSwipe: Boolean val shouldEnablePressToSwipe = Settings.SWIPE_PRESS_TO_ENGAGE.get()
get() = Settings.SWIPE_PRESS_TO_ENGAGE.get()
/** /**
* The threshold for detecting swipe gestures, in pixels. * The threshold for detecting swipe gestures, in pixels.
* Loaded once to ensure consistent behavior during rapid scroll events. * Loaded once to ensure consistent behavior during rapid scroll events.
*/ */
val swipeMagnitudeThreshold: Int val swipeMagnitudeThreshold = Settings.SWIPE_MAGNITUDE_THRESHOLD.get()
get() = Settings.SWIPE_MAGNITUDE_THRESHOLD.get()
/** /**
* The sensitivity of volume swipe gestures, determining how much volume changes per swipe. * The sensitivity of volume swipe gestures, determining how much volume changes per swipe.
* Resets to default if set to 0, as it would disable swiping. * Resets to default if set to 0, as it would disable swiping.
*/ */
val volumeSwipeSensitivity: Int val volumeSwipeSensitivity: Int by lazy {
get() { val sensitivity = Settings.SWIPE_VOLUME_SENSITIVITY.get()
val sensitivity = Settings.SWIPE_VOLUME_SENSITIVITY.get()
if (sensitivity < 1) { if (sensitivity < 1) {
return Settings.SWIPE_VOLUME_SENSITIVITY.resetToDefault() return@lazy Settings.SWIPE_VOLUME_SENSITIVITY.resetToDefault()
}
return sensitivity
} }
sensitivity
}
//endregion //endregion
//region overlay adjustments //region overlay adjustments
/** /**
* Indicates whether haptic feedback should be enabled for swipe control interactions. * Indicates whether haptic feedback should be enabled for swipe control interactions.
*/ */
val shouldEnableHapticFeedback: Boolean val shouldEnableHapticFeedback = Settings.SWIPE_HAPTIC_FEEDBACK.get()
get() = Settings.SWIPE_HAPTIC_FEEDBACK.get()
/** /**
* The duration in milliseconds that the overlay should remain visible after a change. * The duration in milliseconds that the overlay should remain visible after a change.
*/ */
val overlayShowTimeoutMillis: Long val overlayShowTimeoutMillis = Settings.SWIPE_OVERLAY_TIMEOUT.get()
get() = Settings.SWIPE_OVERLAY_TIMEOUT.get()
/** /**
* The background opacity of the overlay, converted from a percentage (0-100) to an alpha value (0-255). * The background opacity of the overlay, converted from a percentage (0-100) to an alpha value (0-255).
* Resets to default and shows a toast if the value is out of range. * Resets to default and shows a toast if the value is out of range.
*/ */
val overlayBackgroundOpacity: Int val overlayBackgroundOpacity: Int by lazy {
get() { var opacity = Settings.SWIPE_OVERLAY_OPACITY.get()
var opacity = Settings.SWIPE_OVERLAY_OPACITY.get()
if (opacity < 0 || opacity > 100) { if (opacity < 0 || opacity > 100) {
Utils.showToastLong(str("revanced_swipe_overlay_background_opacity_invalid_toast")) Utils.showToastLong(str("revanced_swipe_overlay_background_opacity_invalid_toast"))
opacity = Settings.SWIPE_OVERLAY_OPACITY.resetToDefault() opacity = Settings.SWIPE_OVERLAY_OPACITY.resetToDefault()
}
opacity = opacity * 255 / 100
return Color.argb(opacity, 0, 0, 0)
} }
opacity = opacity * 255 / 100
Color.argb(opacity, 0, 0, 0)
}
/** /**
* The color of the progress bar in the overlay. * The color of the progress bar in the overlay for brightness.
* Resets to default and shows a toast if the color string is invalid or empty. * Resets to default and shows a toast if the color string is invalid or empty.
*/ */
val overlayProgressColor: Int val overlayBrightnessProgressColor: Int by lazy {
get() { // Use lazy to avoid repeat parsing. Changing color requires app restart.
try { getSettingColor(Settings.SWIPE_OVERLAY_BRIGHTNESS_COLOR)
@SuppressLint("UseKtx") }
val color = Color.parseColor(Settings.SWIPE_OVERLAY_PROGRESS_COLOR.get())
return (0xBF000000.toInt() or (color and 0xFFFFFF)) /**
} catch (ex: IllegalArgumentException) { * The color of the progress bar in the overlay for volume.
Logger.printDebug({ "Could not parse color" }, ex) * Resets to default and shows a toast if the color string is invalid or empty.
Utils.showToastLong(str("revanced_swipe_overlay_progress_color_invalid_toast")) */
Settings.SWIPE_OVERLAY_PROGRESS_COLOR.resetToDefault() val overlayVolumeProgressColor: Int by lazy {
return overlayProgressColor // Recursively return. getSettingColor(Settings.SWIPE_OVERLAY_VOLUME_COLOR)
} }
private fun getSettingColor(setting: StringSetting): Int {
try {
//noinspection UseKtx
val color = Color.parseColor(setting.get())
return (0xBF000000.toInt() or (color and 0x00FFFFFF))
} catch (ex: IllegalArgumentException) {
// This code should never be reached.
// Color picker rejects and will not save bad colors to a setting.
// If a user imports bad data, the color picker preference resets the
// bad color before this method can be called.
Logger.printDebug({ "Could not parse color: $setting" }, ex)
Utils.showToastLong(str("revanced_settings_color_invalid"))
setting.resetToDefault()
return getSettingColor(setting) // Recursively return.
} }
}
/** /**
* The background color used for the filled portion of the progress bar in the overlay. * The background color used for the filled portion of the progress bar in the overlay.
*/ */
val overlayFillBackgroundPaint: Int val overlayFillBackgroundPaint = 0x80D3D3D3.toInt()
get() = 0x80D3D3D3.toInt()
/** /**
* The color used for text and icons in the overlay. * The color used for text and icons in the overlay.
*/ */
val overlayTextColor: Int val overlayTextColor = Color.WHITE
get() = Color.WHITE
/** /**
* The text size in the overlay, in density-independent pixels (dp). * The text size in the overlay, in density-independent pixels (dp).
* Must be between 1 and 30 dp; resets to default and shows a toast if invalid. * Must be between 1 and 30 dp; resets to default and shows a toast if invalid.
*/ */
val overlayTextSize: Int val overlayTextSize: Int by lazy {
get() { val size = Settings.SWIPE_OVERLAY_TEXT_SIZE.get()
val size = Settings.SWIPE_OVERLAY_TEXT_SIZE.get() if (size < 1 || size > 30) {
if (size < 1 || size > 30) { Utils.showToastLong(str("revanced_swipe_text_overlay_size_invalid_toast"))
Utils.showToastLong(str("revanced_swipe_text_overlay_size_invalid_toast")) return@lazy Settings.SWIPE_OVERLAY_TEXT_SIZE.resetToDefault()
return Settings.SWIPE_OVERLAY_TEXT_SIZE.resetToDefault()
}
return size
} }
size
}
/** /**
* Defines the style of the swipe controls overlay, determining its layout and appearance. * Defines the style of the swipe controls overlay, determining its layout and appearance.
@@ -199,28 +206,25 @@ class SwipeControlsConfigurationProvider {
/** /**
* A minimal vertical progress bar. * A minimal vertical progress bar.
*/ */
VERTICAL_MINIMAL(isMinimal = true, isVertical = true) VERTICAL_MINIMAL(isMinimal = true, isVertical = true)
} }
/** /**
* The current style of the overlay, determining its layout and appearance. * The current style of the overlay, determining its layout and appearance.
*/ */
val overlayStyle: SwipeOverlayStyle val overlayStyle = Settings.SWIPE_OVERLAY_STYLE.get()
get() = Settings.SWIPE_OVERLAY_STYLE.get()
//endregion //endregion
//region behaviour //region behaviour
/** /**
* Indicates whether the brightness level should be saved and restored when entering or exiting fullscreen mode. * Indicates whether the brightness level should be saved and restored when entering or exiting fullscreen mode.
*/ */
val shouldSaveAndRestoreBrightness: Boolean val shouldSaveAndRestoreBrightness = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get()
get() = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get()
/** /**
* Indicates whether auto-brightness should be enabled when the brightness gesture reaches its lowest value. * Indicates whether auto-brightness should be enabled when the brightness gesture reaches its lowest value.
*/ */
val shouldLowestValueEnableAutoBrightness: Boolean val shouldLowestValueEnableAutoBrightness = Settings.SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS.get()
get() = Settings.SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS.get()
/** /**
* The saved brightness value for the swipe gesture, used to restore brightness in fullscreen mode. * The saved brightness value for the swipe gesture, used to restore brightness in fullscreen mode.

View File

@@ -39,7 +39,7 @@ class SwipeControlsOverlayLayout(
constructor(context: Context) : this(context, SwipeControlsConfigurationProvider()) constructor(context: Context) : this(context, SwipeControlsConfigurationProvider())
// Drawable icons for brightness and volume // Drawable icons for brightness and volume.
private val autoBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_auto") private val autoBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_auto")
private val lowBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_low") private val lowBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_low")
private val mediumBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_medium") private val mediumBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_medium")
@@ -50,7 +50,7 @@ class SwipeControlsOverlayLayout(
private val normalVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_normal") private val normalVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_normal")
private val fullVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_high") private val fullVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_high")
// Function to retrieve drawable resources by name // Function to retrieve drawable resources by name.
private fun getDrawable(name: String): Drawable { private fun getDrawable(name: String): Drawable {
val drawable = resources.getDrawable( val drawable = resources.getDrawable(
Utils.getResourceIdentifier(context, name, "drawable"), Utils.getResourceIdentifier(context, name, "drawable"),
@@ -60,19 +60,19 @@ class SwipeControlsOverlayLayout(
return drawable return drawable
} }
// Initialize progress bars // Initialize progress bars.
private val circularProgressView: CircularProgressView private val circularProgressView: CircularProgressView
private val horizontalProgressView: HorizontalProgressView private val horizontalProgressView: HorizontalProgressView
private val verticalBrightnessProgressView: VerticalProgressView private val verticalBrightnessProgressView: VerticalProgressView
private val verticalVolumeProgressView: VerticalProgressView private val verticalVolumeProgressView: VerticalProgressView
init { init {
// Initialize circular progress bar // Initialize circular progress bar.
circularProgressView = CircularProgressView( circularProgressView = CircularProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayBrightnessProgressColor, // Placeholder, updated in showFeedbackView.
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor, config.overlayTextColor,
config.overlayTextSize config.overlayTextSize
@@ -80,18 +80,18 @@ class SwipeControlsOverlayLayout(
layoutParams = LayoutParams(100f.toDisplayPixels().toInt(), 100f.toDisplayPixels().toInt()).apply { layoutParams = LayoutParams(100f.toDisplayPixels().toInt(), 100f.toDisplayPixels().toInt()).apply {
addRule(CENTER_IN_PARENT, TRUE) addRule(CENTER_IN_PARENT, TRUE)
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden.
} }
addView(circularProgressView) addView(circularProgressView)
// Initialize horizontal progress bar // Initialize horizontal progress bar.
val screenWidth = resources.displayMetrics.widthPixels val screenWidth = resources.displayMetrics.widthPixels
val layoutWidth = (screenWidth * 4 / 5).toInt() // Cap at ~360dp val layoutWidth = (screenWidth * 4 / 5).toInt() // Cap at ~360dp.
horizontalProgressView = HorizontalProgressView( horizontalProgressView = HorizontalProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayBrightnessProgressColor, // Placeholder, updated in showFeedbackView.
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor, config.overlayTextColor,
config.overlayTextSize config.overlayTextSize
@@ -104,16 +104,16 @@ class SwipeControlsOverlayLayout(
topMargin = 20f.toDisplayPixels().toInt() topMargin = 20f.toDisplayPixels().toInt()
} }
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden.
} }
addView(horizontalProgressView) addView(horizontalProgressView)
// Initialize vertical progress bar for brightness (right side) // Initialize vertical progress bar for brightness (right side).
verticalBrightnessProgressView = VerticalProgressView( verticalBrightnessProgressView = VerticalProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayBrightnessProgressColor,
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor, config.overlayTextColor,
config.overlayTextSize config.overlayTextSize
@@ -123,16 +123,16 @@ class SwipeControlsOverlayLayout(
rightMargin = 40f.toDisplayPixels().toInt() rightMargin = 40f.toDisplayPixels().toInt()
addRule(CENTER_VERTICAL) addRule(CENTER_VERTICAL)
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden.
} }
addView(verticalBrightnessProgressView) addView(verticalBrightnessProgressView)
// Initialize vertical progress bar for volume (left side) // Initialize vertical progress bar for volume (left side).
verticalVolumeProgressView = VerticalProgressView( verticalVolumeProgressView = VerticalProgressView(
context, context,
config.overlayBackgroundOpacity, config.overlayBackgroundOpacity,
config.overlayStyle.isMinimal, config.overlayStyle.isMinimal,
config.overlayProgressColor, config.overlayVolumeProgressColor,
config.overlayFillBackgroundPaint, config.overlayFillBackgroundPaint,
config.overlayTextColor, config.overlayTextColor,
config.overlayTextSize config.overlayTextSize
@@ -142,12 +142,12 @@ class SwipeControlsOverlayLayout(
leftMargin = 40f.toDisplayPixels().toInt() leftMargin = 40f.toDisplayPixels().toInt()
addRule(CENTER_VERTICAL) addRule(CENTER_VERTICAL)
} }
visibility = GONE // Initially hidden visibility = GONE // Initially hidden.
} }
addView(verticalVolumeProgressView) addView(verticalVolumeProgressView)
} }
// Handler and callback for hiding progress bars // Handler and callback for hiding progress bars.
private val feedbackHideHandler = Handler(Looper.getMainLooper()) private val feedbackHideHandler = Handler(Looper.getMainLooper())
private val feedbackHideCallback = Runnable { private val feedbackHideCallback = Runnable {
circularProgressView.visibility = GONE circularProgressView.visibility = GONE
@@ -165,29 +165,42 @@ class SwipeControlsOverlayLayout(
val viewToShow = when { val viewToShow = when {
config.overlayStyle.isCircular -> circularProgressView config.overlayStyle.isCircular -> circularProgressView
config.overlayStyle.isVertical -> if (isBrightness) verticalBrightnessProgressView else verticalVolumeProgressView config.overlayStyle.isVertical ->
if (isBrightness)
verticalBrightnessProgressView
else
verticalVolumeProgressView
else -> horizontalProgressView else -> horizontalProgressView
} }
viewToShow.apply { viewToShow.apply {
// Set the appropriate progress color.
if (this is CircularProgressView || this is HorizontalProgressView) {
setProgressColor(
if (isBrightness)
config.overlayBrightnessProgressColor
else
config.overlayVolumeProgressColor
)
}
setProgress(progress, max, value, isBrightness) setProgress(progress, max, value, isBrightness)
this.icon = icon this.icon = icon
visibility = VISIBLE visibility = VISIBLE
} }
} }
// Handle volume change // Handle volume change.
override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) { override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) {
val volumePercentage = (newVolume.toFloat() / maximumVolume) * 100 val volumePercentage = (newVolume.toFloat() / maximumVolume) * 100
val icon = when { val icon = when {
newVolume == 0 -> mutedVolumeIcon newVolume == 0 -> mutedVolumeIcon
volumePercentage < 33 -> lowVolumeIcon volumePercentage < 25 -> lowVolumeIcon
volumePercentage < 66 -> normalVolumeIcon volumePercentage < 50 -> normalVolumeIcon
else -> fullVolumeIcon else -> fullVolumeIcon
} }
showFeedbackView("$newVolume", newVolume, maximumVolume, icon, isBrightness = false) showFeedbackView("$newVolume", newVolume, maximumVolume, icon, isBrightness = false)
} }
// Handle brightness change // Handle brightness change.
override fun onBrightnessChanged(brightness: Double) { override fun onBrightnessChanged(brightness: Double) {
if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) { if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) {
val displayText = if (config.overlayStyle.isVertical) "А" val displayText = if (config.overlayStyle.isVertical) "А"
@@ -195,18 +208,19 @@ class SwipeControlsOverlayLayout(
showFeedbackView(displayText, 0, 100, autoBrightnessIcon, isBrightness = true) showFeedbackView(displayText, 0, 100, autoBrightnessIcon, isBrightness = true)
} else { } else {
val brightnessValue = round(brightness).toInt() val brightnessValue = round(brightness).toInt()
val clampedProgress = max(0, brightnessValue)
val icon = when { val icon = when {
brightnessValue < 25 -> lowBrightnessIcon clampedProgress < 25 -> lowBrightnessIcon
brightnessValue < 50 -> mediumBrightnessIcon clampedProgress < 50 -> mediumBrightnessIcon
brightnessValue < 75 -> highBrightnessIcon clampedProgress < 75 -> highBrightnessIcon
else -> fullBrightnessIcon else -> fullBrightnessIcon
} }
val displayText = if (config.overlayStyle.isVertical) "$brightnessValue" else "$brightnessValue%" val displayText = if (config.overlayStyle.isVertical) "$clampedProgress" else "$clampedProgress%"
showFeedbackView(displayText, brightnessValue, 100, icon, isBrightness = true) showFeedbackView(displayText, clampedProgress, 100, icon, isBrightness = true)
} }
} }
// Begin swipe session // Begin swipe session.
override fun onEnterSwipeSession() { override fun onEnterSwipeSession() {
if (config.shouldEnableHapticFeedback) { if (config.shouldEnableHapticFeedback) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
@@ -233,25 +247,41 @@ abstract class AbstractProgressView(
defStyleAttr: Int = 0 defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) { ) : View(context, attrs, defStyleAttr) {
// Combined paint creation function for both fill and stroke styles // Combined paint creation function for both fill and stroke styles.
private fun createPaint(color: Int, style: Paint.Style = Paint.Style.FILL, strokeCap: Paint.Cap = Paint.Cap.BUTT, strokeWidth: Float = 0f) = Paint(Paint.ANTI_ALIAS_FLAG).apply { private fun createPaint(
color: Int,
style: Paint.Style = Paint.Style.FILL,
strokeCap: Paint.Cap = Paint.Cap.BUTT,
strokeWidth: Float = 0f
) = Paint(Paint.ANTI_ALIAS_FLAG).apply {
this.style = style this.style = style
this.color = color this.color = color
this.strokeCap = strokeCap this.strokeCap = strokeCap
this.strokeWidth = strokeWidth this.strokeWidth = strokeWidth
} }
// Initialize paints // Initialize paints.
val backgroundPaint = createPaint(overlayBackgroundOpacity, style = Paint.Style.FILL) val backgroundPaint = createPaint(
val progressPaint = createPaint(overlayProgressColor, style = Paint.Style.STROKE, strokeCap = Paint.Cap.ROUND, strokeWidth = 6f.toDisplayPixels()) overlayBackgroundOpacity,
val fillBackgroundPaint = createPaint(overlayFillBackgroundPaint, style = Paint.Style.FILL) style = Paint.Style.FILL
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { )
color = overlayTextColor val progressPaint = createPaint(
overlayProgressColor,
style = Paint.Style.STROKE,
strokeCap = Paint.Cap.ROUND,
strokeWidth = 6f.toDisplayPixels()
)
val fillBackgroundPaint = createPaint(
overlayFillBackgroundPaint,
style = Paint.Style.FILL
)
val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = overlayTextColor
textAlign = Paint.Align.CENTER textAlign = Paint.Align.CENTER
textSize = overlayTextSize.toFloat().toDisplayPixels() textSize = overlayTextSize.toFloat().toDisplayPixels()
} }
// Rect for text measurement // Rect for text measurement.
protected val textBounds = Rect() protected val textBounds = Rect()
protected var progress = 0 protected var progress = 0
@@ -268,13 +298,18 @@ abstract class AbstractProgressView(
invalidate() invalidate()
} }
fun setProgressColor(color: Int) {
progressPaint.color = color
invalidate()
}
protected fun measureTextWidth(text: String, paint: Paint): Int { protected fun measureTextWidth(text: String, paint: Paint): Int {
paint.getTextBounds(text, 0, text.length, textBounds) paint.getTextBounds(text, 0, text.length, textBounds)
return textBounds.width() return textBounds.width()
} }
override fun onDraw(canvas: Canvas) { override fun onDraw(canvas: Canvas) {
// Base class implementation can be empty // Base class implementation can be empty.
} }
} }
@@ -393,8 +428,8 @@ class HorizontalProgressView(
} }
/** /**
* Calculate required width based on content * Calculate required width based on content.
* @return Required width to display all elements * @return Required width to display all elements.
*/ */
private fun calculateRequiredWidth(): Float { private fun calculateRequiredWidth(): Float {
textWidth = measureTextWidth(displayText, textPaint).toFloat() textWidth = measureTextWidth(displayText, textPaint).toFloat()
@@ -537,8 +572,8 @@ class VerticalProgressView(
} }
/** /**
* Calculate required height based on content * Calculate required height based on content.
* @return Required height to display all elements * @return Required height to display all elements.
*/ */
private fun calculateRequiredHeight(): Float { private fun calculateRequiredHeight(): Float {
return if (!isMinimalStyle) { return if (!isMinimalStyle) {

View File

@@ -5,9 +5,13 @@ import android.view.View;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch; import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.shared.Utils.showToastShort;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PlaybackSpeedDialogButton { public class PlaybackSpeedDialogButton {
@Nullable @Nullable
@@ -23,8 +27,27 @@ public class PlaybackSpeedDialogButton {
"revanced_playback_speed_dialog_button", "revanced_playback_speed_dialog_button",
"revanced_playback_speed_dialog_button_placeholder", "revanced_playback_speed_dialog_button_placeholder",
Settings.PLAYBACK_SPEED_DIALOG_BUTTON::get, Settings.PLAYBACK_SPEED_DIALOG_BUTTON::get,
view -> CustomPlaybackSpeedPatch.showOldPlaybackSpeedMenu(), view -> {
null try {
CustomPlaybackSpeedPatch.showModernCustomPlaybackSpeedDialog(view.getContext());
} catch (Exception ex) {
Logger.printException(() -> "speed button onClick failure", ex);
}
},
view -> {
try {
final float speed = (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.get() ||
VideoInformation.getPlaybackSpeed() == Settings.PLAYBACK_SPEED_DEFAULT.get())
? 1.0f
: Settings.PLAYBACK_SPEED_DEFAULT.get();
VideoInformation.overridePlaybackSpeed(speed);
showToastShort(str("revanced_custom_playback_speeds_reset_toast", (speed + "x")));
} catch (Exception ex) {
Logger.printException(() -> "speed button reset failure", ex);
}
return true;
}
); );
} catch (Exception ex) { } catch (Exception ex) {
Logger.printException(() -> "initializeButton failure", ex); Logger.printException(() -> "initializeButton failure", ex);

View File

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

58
package-lock.json generated
View File

@@ -9,7 +9,7 @@
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.10.1", "gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.2.1" "semantic-release": "^24.2.5"
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
@@ -1964,9 +1964,9 @@
} }
}, },
"node_modules/chalk": { "node_modules/chalk": {
"version": "5.3.0", "version": "5.4.1",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -3460,9 +3460,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/marked": { "node_modules/marked": {
"version": "12.0.2", "version": "15.0.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz",
"integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
@@ -3473,24 +3473,38 @@
} }
}, },
"node_modules/marked-terminal": { "node_modules/marked-terminal": {
"version": "7.1.0", "version": "7.3.0",
"resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.1.0.tgz", "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz",
"integrity": "sha512-+pvwa14KZL74MVXjYdPR3nSInhGhNvPce/3mqLVZT2oUvt654sL1XImFuLZ1pkA866IYZ3ikDTOFUIC7XzpZZg==", "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ansi-escapes": "^7.0.0", "ansi-escapes": "^7.0.0",
"chalk": "^5.3.0", "ansi-regex": "^6.1.0",
"chalk": "^5.4.1",
"cli-highlight": "^2.1.11", "cli-highlight": "^2.1.11",
"cli-table3": "^0.6.5", "cli-table3": "^0.6.5",
"node-emoji": "^2.1.3", "node-emoji": "^2.2.0",
"supports-hyperlinks": "^3.0.0" "supports-hyperlinks": "^3.1.0"
}, },
"engines": { "engines": {
"node": ">=16.0.0" "node": ">=16.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"marked": ">=1 <14" "marked": ">=1 <16"
}
},
"node_modules/marked-terminal/node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
} }
}, },
"node_modules/meow": { "node_modules/meow": {
@@ -3607,9 +3621,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-emoji": { "node_modules/node-emoji": {
"version": "2.1.3", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.1.3.tgz", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz",
"integrity": "sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==", "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6760,9 +6774,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/semantic-release": { "node_modules/semantic-release": {
"version": "24.2.1", "version": "24.2.5",
"resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.1.tgz", "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.5.tgz",
"integrity": "sha512-z0/3cutKNkLQ4Oy0HTi3lubnjTsdjjgOqmxdPjeYWe6lhFqUPfwslZxRHv3HDZlN4MhnZitb9SLihDkZNxOXfQ==", "integrity": "sha512-9xV49HNY8C0/WmPWxTlaNleiXhWb//qfMzG2c5X8/k7tuWcu8RssbuS+sujb/h7PiWSXv53mrQvV9hrO9b7vuQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -6784,8 +6798,8 @@
"hosted-git-info": "^8.0.0", "hosted-git-info": "^8.0.0",
"import-from-esm": "^2.0.0", "import-from-esm": "^2.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"marked": "^12.0.0", "marked": "^15.0.0",
"marked-terminal": "^7.0.0", "marked-terminal": "^7.3.0",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
"p-each-series": "^3.0.0", "p-each-series": "^3.0.0",
"p-reduce": "^3.0.0", "p-reduce": "^3.0.0",

View File

@@ -4,6 +4,6 @@
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.10.1", "gradle-semantic-release-plugin": "^1.10.1",
"semantic-release": "^24.2.1" "semantic-release": "^24.2.5"
} }
} }

View File

@@ -1,7 +1,3 @@
public final class FixFacebookLoginPatchKt {
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/all/misc/activity/exportall/ExportAllActivitiesPatchKt { public final class app/revanced/patches/all/misc/activity/exportall/ExportAllActivitiesPatchKt {
public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
} }
@@ -10,6 +6,10 @@ public final class app/revanced/patches/all/misc/adb/HideAdbPatchKt {
public static final fun getHideAdbStatusPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getHideAdbStatusPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/all/misc/appicon/HideAppIconPatchKt {
public static final fun getHideAppIconPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt { public final class app/revanced/patches/all/misc/build/BaseSpoofBuildInfoPatchKt {
public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch; public static final fun baseSpoofBuildInfoPatch (Lkotlin/jvm/functions/Function0;)Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -196,6 +196,10 @@ public final class app/revanced/patches/googlenews/misc/gms/GmsCoreSupportPatchK
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/googlephotos/misc/backup/EnableDCIMFoldersBackupControlPatchKt {
public static final fun getEnableDCIMFoldersBackupControlPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/googlephotos/misc/extension/ExtensionPatchKt { public final class app/revanced/patches/googlephotos/misc/extension/ExtensionPatchKt {
public static final fun getExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -288,6 +292,10 @@ public final class app/revanced/patches/messenger/inputfield/DisableTypingIndica
public static final fun getDisableTypingIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getDisableTypingIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/messenger/layout/HideFacebookButtonPatchKt {
public static final fun getHideFacebookButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/messenger/metaai/RemoveMetaAIPatchKt { public final class app/revanced/patches/messenger/metaai/RemoveMetaAIPatchKt {
public static final fun getRemoveMetaAIPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveMetaAIPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -300,6 +308,10 @@ public final class app/revanced/patches/messenger/navbar/RemoveMetaAITabPatchKt
public static final fun getRemoveMetaAITabPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveMetaAITabPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/meta/ads/HideAdsPatchKt {
public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/mifitness/misc/locale/ForceEnglishLocalePatchKt { public final class app/revanced/patches/mifitness/misc/locale/ForceEnglishLocalePatchKt {
public static final fun getForceEnglishLocalePatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getForceEnglishLocalePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -444,6 +456,14 @@ public final class app/revanced/patches/primevideo/misc/extension/ExtensionPatch
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/primevideo/misc/permissions/RenamePermissionsPatchKt {
public static final fun getRenamePermissionsPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/protonmail/account/RemoveFreeAccountsLimitPatchKt {
public static final fun getRemoveFreeAccountsLimitPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/protonmail/signature/RemoveSentFromSignaturePatchKt { public final class app/revanced/patches/protonmail/signature/RemoveSentFromSignaturePatchKt {
public static final fun getRemoveSentFromSignaturePatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun getRemoveSentFromSignaturePatch ()Lapp/revanced/patcher/patch/ResourcePatch;
} }
@@ -567,6 +587,10 @@ public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/
public static final fun getFixSLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getFixSLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/fix/thumbnail/FixPostThumbnailsPatchKt {
public static final fun getFixPostThumbnailsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/fix/user/UseUserEndpointPatchKt { public final class app/revanced/patches/reddit/customclients/sync/syncforreddit/fix/user/UseUserEndpointPatchKt {
public static final fun getUseUserEndpointPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getUseUserEndpointPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -585,6 +609,7 @@ public final class app/revanced/patches/reddit/layout/disablescreenshotpopup/Dis
public final class app/revanced/patches/reddit/layout/premiumicon/UnlockPremiumIconPatchKt { public final class app/revanced/patches/reddit/layout/premiumicon/UnlockPremiumIconPatchKt {
public static final fun getUnlockPremiumIconPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getUnlockPremiumIconPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
public static final fun getUnlockPremiumIconsPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/reddit/misc/extension/ExtensionPatchKt { public final class app/revanced/patches/reddit/misc/extension/ExtensionPatchKt {
@@ -864,6 +889,10 @@ public final class app/revanced/patches/soundcloud/offlinesync/EnableOfflineSync
public static final fun getEnableOfflineSync ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getEnableOfflineSync ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/spotify/layout/hide/createbutton/HideCreateButtonPatchKt {
public static final fun getHideCreateButtonPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/layout/theme/CustomThemePatchKt { public final class app/revanced/patches/spotify/layout/theme/CustomThemePatchKt {
public static final fun getCustomThemePatch ()Lapp/revanced/patcher/patch/ResourcePatch; public static final fun getCustomThemePatch ()Lapp/revanced/patcher/patch/ResourcePatch;
} }
@@ -888,6 +917,10 @@ public final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt {
public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/spotify/misc/fix/login/FixFacebookLoginPatchKt {
public static final fun getFixFacebookLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt { public final class app/revanced/patches/spotify/misc/privacy/SanitizeSharingLinksPatchKt {
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -1399,6 +1432,10 @@ public final class app/revanced/patches/youtube/misc/gms/GmsCoreSupportPatchKt {
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/youtube/misc/hapticfeedback/DisableHapticFeedbackPatchKt {
public static final fun getDisableHapticFeedbackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHookKt { public final class app/revanced/patches/youtube/misc/imageurlhook/CronetImageUrlHookKt {
public static final fun addImageUrlErrorCallbackHook (Ljava/lang/String;)V public static final fun addImageUrlErrorCallbackHook (Ljava/lang/String;)V
public static final fun addImageUrlHook (Ljava/lang/String;Z)V public static final fun addImageUrlHook (Ljava/lang/String;Z)V

View File

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

View File

@@ -7,7 +7,7 @@ import app.revanced.patcher.patch.stringOption
@Suppress("unused") @Suppress("unused")
val spoofBuildInfoPatch = bytecodePatch( val spoofBuildInfoPatch = bytecodePatch(
name = "Spoof build info", name = "Spoof build info",
description = "Spoof the information about the current build.", description = "Spoofs the information about the current build.",
use = false, use = false,
) { ) {
val board by stringOption( val board by stringOption(
@@ -141,14 +141,14 @@ val spoofBuildInfoPatch = bytecodePatch(
val socManufacturer by stringOption( val socManufacturer by stringOption(
key = "soc-manufacturer", key = "soc-manufacturer",
default = null, default = null,
title = "SOC Manufacturer", title = "SOC manufacturer",
description = "The manufacturer of the device's primary system-on-chip.", description = "The manufacturer of the device's primary system-on-chip.",
) )
val socModel by stringOption( val socModel by stringOption(
key = "soc-model", key = "soc-model",
default = null, default = null,
title = "SOC Model", title = "SOC model",
description = "The model name of the device's primary system-on-chip.", description = "The model name of the device's primary system-on-chip.",
) )

View File

@@ -36,12 +36,12 @@ val spoofSimCountryPatch = bytecodePatch(
val networkCountryIso by isoCountryPatchOption( val networkCountryIso by isoCountryPatchOption(
"networkCountryIso", "networkCountryIso",
"Network ISO Country Code", "Network ISO country code",
) )
val simCountryIso by isoCountryPatchOption( val simCountryIso by isoCountryPatchOption(
"simCountryIso", "simCountryIso",
"Sim ISO Country Code", "SIM ISO country code",
) )
dependsOn( dependsOn(

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,23 +1,9 @@
package app.revanced.patches.instagram.ads package app.revanced.patches.instagram.ads
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@Deprecated("Patch was moved to different package: app.revanced.patches.meta.ads.hideAdsPatch")
@Suppress("unused") @Suppress("unused")
val hideAdsPatch = bytecodePatch( val hideAdsPatch = bytecodePatch {
name = "Hide ads", dependsOn(app.revanced.patches.meta.ads.hideAdsPatch)
description = "Hides ads in stories, discover, profile, etc. " +
"An ad can still appear once when refreshing the home feed.",
) {
compatibleWith("com.instagram.android")
execute {
adInjectorFingerprint.method.addInstructions(
0,
"""
const/4 v0, 0x0
return v0
""",
)
}
} }

View File

@@ -5,7 +5,7 @@ import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused") @Suppress("unused")
val unlockPremiumPatch = bytecodePatch( val unlockPremiumPatch = bytecodePatch(
name = "Unlock premium", name = "Unlock Premium",
) { ) {
compatibleWith("com.adobe.lrmobile"("10.0.2")) compatibleWith("com.adobe.lrmobile"("10.0.2"))

View File

@@ -5,9 +5,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
/**
* This patch will be deleted soon.
*
* Pull requests to update this patch to the latest app target are invited.
*/
@Deprecated("This patch only works with an outdated app target that is no longer fully supported by Facebook.")
@Suppress("unused") @Suppress("unused")
val disableSwitchingEmojiToStickerPatch = bytecodePatch( val disableSwitchingEmojiToStickerPatch = bytecodePatch(
name = "Disable switching emoji to sticker",
description = "Disables switching from emoji to sticker search mode in message input field.", description = "Disables switching from emoji to sticker search mode in message input field.",
) { ) {
compatibleWith("com.facebook.orca"("439.0.0.29.119")) compatibleWith("com.facebook.orca"("439.0.0.29.119"))

View File

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

View File

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

View File

@@ -7,8 +7,7 @@ internal val getMobileConfigBoolFingerprint = fingerprint {
parameters("J") parameters("J")
returns("Z") returns("Z")
opcodes(Opcode.RETURN) opcodes(Opcode.RETURN)
custom { method, classDef -> custom { _, classDef ->
method.implementation ?: return@custom false // unsure if this is necessary
classDef.interfaces.contains("Lcom/facebook/mobileconfig/factory/MobileConfigUnsafeContext;") classDef.interfaces.contains("Lcom/facebook/mobileconfig/factory/MobileConfigUnsafeContext;")
} }
} }

View File

@@ -25,7 +25,7 @@ val removeMetaAIPatch = bytecodePatch(
addInstructions( addInstructions(
returnIndex, returnIndex,
""" """
invoke-static { p1, p2, v$returnRegister }, $EXTENSION_CLASS_DESCRIPTOR->overrideConfigBool(JZ)Z invoke-static { p1, p2, v$returnRegister }, $EXTENSION_CLASS_DESCRIPTOR->overrideBooleanFlag(JZ)Z
move-result v$returnRegister move-result v$returnRegister
""" """
) )

View File

@@ -1,4 +1,4 @@
package app.revanced.patches.instagram.ads package app.revanced.patches.meta.ads
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags

View File

@@ -0,0 +1,21 @@
package app.revanced.patches.meta.ads
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.returnEarly
@Suppress("unused")
val hideAdsPatch = bytecodePatch(
name = "Hide ads",
) {
/**
* Patch is identical for both Instagram and Threads app.
*/
compatibleWith(
"com.instagram.android",
"com.instagram.barcelona",
)
execute {
adInjectorFingerprint.method.returnEarly(false)
}
}

View File

@@ -0,0 +1,44 @@
package app.revanced.patches.primevideo.misc.permissions
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.asSequence
import app.revanced.util.getNode
import org.w3c.dom.Element
@Suppress("unused")
val renamePermissionsPatch = resourcePatch(
name = "Rename shared permissions",
description = "Rename certain permissions shared across Amazon apps. " +
"Applying this patch can fix installation errors, but can also break features in certain apps.",
use = false
) {
compatibleWith("com.amazon.avod.thirdpartyclient")
val permissionNames = setOf(
"com.amazon.identity.permission.CAN_CALL_MAP_INFORMATION_PROVIDER",
"com.amazon.identity.auth.device.perm.AUTH_SDK",
"com.amazon.dcp.sso.permission.account.changed",
"com.amazon.dcp.sso.permission.AmazonAccountPropertyService.property.changed",
"com.amazon.identity.permission.CALL_AMAZON_DEVICE_INFORMATION_PROVIDER",
"com.amazon.appmanager.preload.permission.READ_PRELOAD_DEVICE_INFO_PROVIDER"
)
execute {
document("AndroidManifest.xml").use { document ->
val manifest = document.getNode("manifest") as Element
val permissions = manifest
.getElementsByTagName("permission")
.asSequence()
.map { Pair(it as Element, it.getAttribute("android:name")) }
.filter { (_, name) -> name in permissionNames }
if (permissions.none()) throw PatchException("Could not find any permissions to rename")
permissions.forEach { (element, name) ->
element.setAttribute("android:name", "revanced.$name")
}
}
}
}

View File

@@ -0,0 +1,21 @@
package app.revanced.patches.protonmail.account
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.findElementByAttributeValueOrThrow
@Suppress("unused")
val removeFreeAccountsLimitPatch = resourcePatch(
name = "Remove free accounts limit",
description = "Removes the limit for maximum free accounts logged in.",
) {
compatibleWith("ch.protonmail.android")
execute {
document("res/values/integers.xml").use { document ->
document.documentElement.childNodes.findElementByAttributeValueOrThrow(
"name",
"core_feature_auth_user_check_max_free_user_count",
).textContent = Int.MAX_VALUE.toString()
}
}
}

View File

@@ -0,0 +1,12 @@
package app.revanced.patches.reddit.customclients.sync.syncforreddit.fix.thumbnail
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
internal val customImageViewLoadFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC)
parameters("Ljava/lang/String;", "Z", "Z", "I", "I")
custom { _, classDef ->
classDef.endsWith("CustomImageView;")
}
}

View File

@@ -0,0 +1,31 @@
package app.revanced.patches.reddit.customclients.sync.syncforreddit.fix.thumbnail
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused")
val fixPostThumbnailsPatch = bytecodePatch(
name = "Fix post thumbnails",
description = "Fixes loading post thumbnails by correcting their URLs.",
) {
compatibleWith(
"com.laurencedawson.reddit_sync",
"com.laurencedawson.reddit_sync.pro",
"com.laurencedawson.reddit_sync.dev"
)
// Image URLs contain escaped ampersands (&amp;), let's replace these with unescaped ones (&).
execute {
customImageViewLoadFingerprint.method.addInstructions(
0,
"""
# url = url.replace("&amp;", "&");
const-string v0, "&amp;"
const-string v1, "&"
invoke-virtual { p1, v0, v1 }, Ljava/lang/String;->replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;
move-result-object p1
"""
)
}
}

View File

@@ -4,9 +4,9 @@ import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@Suppress("unused") @Suppress("unused")
val unlockPremiumIconPatch = bytecodePatch( val unlockPremiumIconsPatch = bytecodePatch(
name = "Unlock premium Reddit icons", name = "Unlock Premium icons",
description = "Unlocks the premium Reddit icons.", description = "Unlocks the Reddit Premium icons.",
) { ) {
compatibleWith("com.reddit.frontpage") compatibleWith("com.reddit.frontpage")
@@ -20,3 +20,9 @@ val unlockPremiumIconPatch = bytecodePatch(
) )
} }
} }
@Deprecated("Patch was renamed", ReplaceWith("unlockPremiumIconsPatch"))
@Suppress("unused")
val unlockPremiumIconPatch = bytecodePatch{
dependsOn(unlockPremiumIconsPatch)
}

View File

@@ -8,7 +8,7 @@ import java.util.logging.Logger
@Suppress("unused") @Suppress("unused")
val disableLicenseCheckPatch = bytecodePatch( val disableLicenseCheckPatch = bytecodePatch(
name = "Disable Pairip license check", name = "Disable Pairip license check",
description = "Disable Play Integrity Protect (Pairip) client-side license check.", description = "Disables Play Integrity API (Pairip) client-side license check.",
use = false use = false
) { ) {

View File

@@ -0,0 +1,28 @@
package app.revanced.patches.spotify.layout.hide.createbutton
import app.revanced.patcher.fingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val navigationBarItemSetClassFingerprint = fingerprint {
strings("NavigationBarItemSet(")
}
internal val navigationBarItemSetConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
// Make sure the method checks whether navigation bar items are null before adding them.
// If this is not true, then we cannot patch the method and potentially transform the parameters into null.
opcodes(Opcode.IF_EQZ, Opcode.INVOKE_VIRTUAL)
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<MethodReference>()?.name == "add"
} >= 0
}
}
internal val oldNavigationBarAddItemFingerprint = fingerprint {
strings("Bottom navigation tabs exceeds maximum of 5 tabs")
}

View File

@@ -0,0 +1,110 @@
package app.revanced.patches.spotify.layout.hide.createbutton
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import java.util.logging.Logger
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/spotify/layout/hide/createbutton/HideCreateButtonPatch;"
@Suppress("unused")
val hideCreateButtonPatch = bytecodePatch(
name = "Hide Create button",
description = "Hides the \"Create\" button in the navigation bar."
) {
compatibleWith("com.spotify.music")
dependsOn(sharedExtensionPatch)
execute {
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
Logger.getLogger(this::class.java.name).warning(
"Create button does not exist in legacy app target. No changes applied."
)
return@execute
}
val oldNavigationBarAddItemMethod = oldNavigationBarAddItemFingerprint.originalMethodOrNull
// Only throw the fingerprint error when oldNavigationBarAddItemMethod does not exist.
val navigationBarItemSetClassDef = if (oldNavigationBarAddItemMethod == null) {
navigationBarItemSetClassFingerprint.originalClassDef
} else {
navigationBarItemSetClassFingerprint.originalClassDefOrNull
}
if (navigationBarItemSetClassDef != null) {
// Main patch for newest and most versions.
// The NavigationBarItemSet constructor accepts multiple parameters which represent each navigation bar item.
// Each item is manually checked whether it is not null and then added to a LinkedHashSet.
// Since the order of the items can differ, we are required to check every parameter to see whether it is the
// Create button. So, for every parameter passed to the method, invoke our extension method and overwrite it
// to null in case it is the Create button.
navigationBarItemSetConstructorFingerprint.match(navigationBarItemSetClassDef).method.apply {
// Add 1 to the index because the first parameter register is `this`.
val parameterTypesWithRegister = parameterTypes.mapIndexed { index, parameterType ->
parameterType to (index + 1)
}
val returnNullIfIsCreateButtonDescriptor =
"$EXTENSION_CLASS_DESCRIPTOR->returnNullIfIsCreateButton(Ljava/lang/Object;)Ljava/lang/Object;"
parameterTypesWithRegister.reversed().forEach { (parameterType, parameterRegister) ->
addInstructions(
0,
"""
invoke-static { p$parameterRegister }, $returnNullIfIsCreateButtonDescriptor
move-result-object p$parameterRegister
check-cast p$parameterRegister, $parameterType
"""
)
}
}
}
if (oldNavigationBarAddItemMethod != null) {
// In case an older version of the app is being patched, hook the old method which adds navigation bar items.
// Return null early if the navigation bar item title resource id is the old Create button title resource id.
oldNavigationBarAddItemFingerprint.methodOrNull?.apply {
val getNavigationBarItemTitleStringIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/content/res/Resources;" && reference.name == "getString"
}
// This register is a parameter register, so it can be used at the start of the method when adding
// the new instructions.
val oldNavigationBarItemTitleResIdRegister =
getInstruction<FiveRegisterInstruction>(getNavigationBarItemTitleStringIndex).registerD
// The instruction where the normal method logic starts.
val firstInstruction = getInstruction(0)
val isOldCreateButtonDescriptor =
"$EXTENSION_CLASS_DESCRIPTOR->isOldCreateButton(I)Z"
addInstructionsWithLabels(
0,
"""
invoke-static { v$oldNavigationBarItemTitleResIdRegister }, $isOldCreateButtonDescriptor
move-result v0
# If this navigation bar item is not the Create button, jump to the normal method logic.
if-eqz v0, :normal-method-logic
# Return null early because this method return value is a BottomNavigationItemView.
const/4 v0, 0
return-object v0
""",
ExternalLabel("normal-method-logic", firstInstruction)
)
}
}
}
}

View File

@@ -2,65 +2,19 @@ package app.revanced.patches.spotify.layout.theme
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.booleanOption import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.* import app.revanced.util.*
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import org.w3c.dom.Element import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/layout/theme/CustomThemePatch;" private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/layout/theme/CustomThemePatch;"
internal val spotifyBackgroundColor = stringOption(
key = "backgroundColor",
default = "@android:color/black",
title = "Primary background color",
description = "The background color. Can be a hex color or a resource reference.",
required = true,
)
internal val overridePlayerGradientColor = booleanOption(
key = "overridePlayerGradientColor",
default = false,
title = "Override player gradient color",
description = "Apply primary background color to the player gradient color, which changes dynamically with the song.",
required = false
)
internal val spotifyBackgroundColorSecondary = stringOption(
key = "backgroundColorSecondary",
default = "#FF121212",
title = "Secondary background color",
description =
"The secondary background color. (e.g. playlist list in home, player artist, song credits). Can be a hex color or a resource reference.",
required = true,
)
internal val spotifyAccentColor = stringOption(
key = "accentColor",
default = "#FF1ED760",
title = "Accent color",
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
required = true,
)
internal val spotifyAccentColorPressed = stringOption(
key = "accentColorPressed",
default = "#FF169C46",
title = "Pressed dark theme accent color",
description =
"The color when accented buttons are pressed, by default slightly darker than accent. Can be a hex color or a resource reference.",
required = true,
)
private val customThemeBytecodePatch = bytecodePatch { private val customThemeBytecodePatch = bytecodePatch {
dependsOn(sharedExtensionPatch) dependsOn(sharedExtensionPatch)
@@ -71,60 +25,60 @@ private val customThemeBytecodePatch = bytecodePatch {
return@execute return@execute
} }
fun MutableMethod.addColorChangeInstructions(literal: Long, colorString: String) { val colorSpaceUtilsClassDef = colorSpaceUtilsClassFingerprint.originalClassDef
val index = indexOfFirstLiteralInstructionOrThrow(literal)
val register = getInstruction<OneRegisterInstruction>(index).registerA // Hook a util method that converts ARGB to RGBA in the sRGB color space to replace hardcoded accent colors.
convertArgbToRgbaFingerprint.match(colorSpaceUtilsClassDef).method.apply {
addInstructions(
0,
"""
long-to-int p0, p0
invoke-static { p0 }, $EXTENSION_CLASS_DESCRIPTOR->replaceColor(I)I
move-result p0
int-to-long p0, p0
"""
)
}
// Lottie JSON parser method. It parses the JSON Lottie animation into its own class,
// including the solid color of it.
parseLottieJsonFingerprint.method.apply {
val invokeParseColorIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
reference?.definingClass == "Landroid/graphics/Color;"
&& reference.name == "parseColor"
}
val parsedColorRegister = getInstruction<OneRegisterInstruction>(invokeParseColorIndex + 1).registerA
val replaceColorDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->replaceColor(I)I"
addInstructions( addInstructions(
index + 1, invokeParseColorIndex + 2,
""" """
const-string v$register, "$colorString" # Use invoke-static/range because the register number is too large.
invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->getThemeColor(Ljava/lang/String;)J invoke-static/range { v$parsedColorRegister .. v$parsedColorRegister }, $replaceColorDescriptor
move-result-wide v$register move-result v$parsedColorRegister
""" """
) )
} }
val encoreColorsClassName = with(encoreThemeFingerprint.originalMethod) { // Lottie animated color parser.
// "Encore" colors are referenced right before the value of POSITIVE_INFINITY is returned. parseAnimatedColorFingerprint.method.apply {
// Begin the instruction find using the index of where POSITIVE_INFINITY is set into the register. val invokeArgbIndex = indexOfFirstInstructionOrThrow {
val positiveInfinityIndex = indexOfFirstLiteralInstructionOrThrow( val reference = getReference<MethodReference>()
Float.POSITIVE_INFINITY reference?.definingClass == "Landroid/graphics/Color;"
) && reference.name == "argb"
val encoreColorsFieldReferenceIndex = indexOfFirstInstructionReversedOrThrow(
positiveInfinityIndex,
Opcode.SGET_OBJECT
)
getInstruction(encoreColorsFieldReferenceIndex)
.getReference<FieldReference>()!!.definingClass
}
val encoreColorsConstructorFingerprint = fingerprint {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
custom { method, classDef ->
classDef.type == encoreColorsClassName &&
method.containsLiteralInstruction(PLAYLIST_BACKGROUND_COLOR_LITERAL)
} }
val argbColorRegister = getInstruction<OneRegisterInstruction>(invokeArgbIndex + 1).registerA
addInstructions(
invokeArgbIndex + 2,
"""
invoke-static { v$argbColorRegister }, $EXTENSION_CLASS_DESCRIPTOR->replaceColor(I)I
move-result v$argbColorRegister
"""
)
} }
val backgroundColor by spotifyBackgroundColor
val backgroundColorSecondary by spotifyBackgroundColorSecondary
encoreColorsConstructorFingerprint.method.apply {
addColorChangeInstructions(PLAYLIST_BACKGROUND_COLOR_LITERAL, backgroundColor!!)
addColorChangeInstructions(SHARE_MENU_BACKGROUND_COLOR_LITERAL, backgroundColorSecondary!!)
}
homeCategoryPillColorsFingerprint.method.addColorChangeInstructions(
HOME_CATEGORY_PILL_COLOR_LITERAL,
backgroundColorSecondary!!
)
settingsHeaderColorFingerprint.method.addColorChangeInstructions(
SETTINGS_HEADER_COLOR_LITERAL,
backgroundColorSecondary!!
)
} }
} }
@@ -138,11 +92,48 @@ val customThemePatch = resourcePatch(
dependsOn(customThemeBytecodePatch) dependsOn(customThemeBytecodePatch)
val backgroundColor by spotifyBackgroundColor() val backgroundColor by stringOption(
val overridePlayerGradientColor by overridePlayerGradientColor() key = "backgroundColor",
val backgroundColorSecondary by spotifyBackgroundColorSecondary() default = "@android:color/black",
val accentColor by spotifyAccentColor() title = "Primary background color",
val accentColorPressed by spotifyAccentColorPressed() description = "The background color. Can be a hex color or a resource reference.",
required = true,
)
val overridePlayerGradientColor by booleanOption(
key = "overridePlayerGradientColor",
default = false,
title = "Override player gradient color",
description =
"Apply primary background color to the player gradient color, which changes dynamically with the song.",
required = false,
)
val backgroundColorSecondary by stringOption(
key = "backgroundColorSecondary",
default = "#FF121212",
title = "Secondary background color",
description = "The secondary background color. (e.g. playlist list in home, player artist, song credits). " +
"Can be a hex color or a resource reference.\",",
required = true,
)
val accentColor by stringOption(
key = "accentColor",
default = "#FF1ED760",
title = "Accent color",
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
required = true,
)
val accentColorPressed by stringOption(
key = "accentColorPressed",
default = "#FF1ABC54",
title = "Pressed accent color",
description = "The color when accented buttons are pressed, by default slightly darker than accent. " +
"Can be a hex color or a resource reference.",
required = true,
)
execute { execute {
document("res/values/colors.xml").use { document -> document("res/values/colors.xml").use { document ->
@@ -161,34 +152,41 @@ val customThemePatch = resourcePatch(
} }
node.textContent = when (name) { node.textContent = when (name) {
// Main background color.
"gray_7",
// Left sidebar background color in tablet mode.
"gray_10",
// Gradient next to user photo and "All" in home page. // Gradient next to user photo and "All" in home page.
"dark_base_background_base", "dark_base_background_base",
// Main background. // "Add account", "Settings and privacy", "View Profile" left sidebar background color.
"gray_7",
// Left sidebar background in tablet mode.
"gray_10",
// "Add account", "Settings and privacy", "View Profile" left sidebar background.
"dark_base_background_elevated_base", "dark_base_background_elevated_base",
// Song/player gradient start/end color. // Song/player gradient start/end color.
"bg_gradient_start_color", "bg_gradient_end_color", "bg_gradient_start_color", "bg_gradient_end_color",
// Login screen background and gradient start. // Login screen background color and gradient start.
"sthlm_blk", "sthlm_blk_grad_start", "sthlm_blk", "sthlm_blk_grad_start",
// Misc. // Misc.
"image_placeholder_color", "image_placeholder_color",
-> backgroundColor -> backgroundColor
// Track credits, merch background in song player. // "About the artist" background color in song player.
"gray_15",
// Track credits, merch background color in song player.
"track_credits_card_bg", "benefit_list_default_color", "merch_card_background", "track_credits_card_bg", "benefit_list_default_color", "merch_card_background",
// Playlist list background in home page. // Playlist list background in home page.
"opacity_white_10", "opacity_white_10",
// "About the artist" background in song player.
"gray_15",
// "What's New" pills background. // "What's New" pills background.
"dark_base_background_tinted_highlight" "dark_base_background_tinted_highlight"
-> backgroundColorSecondary -> backgroundColorSecondary
"dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor "dark_brightaccent_background_base",
"dark_brightaccent_background_press" -> accentColorPressed "dark_base_text_brightaccent",
"green_light",
"spotify_green_157"
-> accentColor
"dark_brightaccent_background_press"
-> accentColorPressed
else -> continue else -> continue
} }
} }
@@ -198,8 +196,8 @@ val customThemePatch = resourcePatch(
document("res/drawable/start_screen_gradient.xml").use { document -> document("res/drawable/start_screen_gradient.xml").use { document ->
val gradientNode = document.getElementsByTagName("gradient").item(0) as Element val gradientNode = document.getElementsByTagName("gradient").item(0) as Element
gradientNode.setAttribute("android:startColor", backgroundColor) gradientNode.setAttribute("android:startColor", "@color/gray_7")
gradientNode.setAttribute("android:endColor", backgroundColor) gradientNode.setAttribute("android:endColor", "@color/gray_7")
} }
} }
} }

View File

@@ -4,30 +4,25 @@ import app.revanced.patcher.fingerprint
import app.revanced.util.containsLiteralInstruction import app.revanced.util.containsLiteralInstruction
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal val encoreThemeFingerprint = fingerprint { internal val colorSpaceUtilsClassFingerprint = fingerprint {
strings("Encore theme was not provided.") // Partial string match. strings("The specified color must be encoded in an RGB color space.") // Partial string match.
custom { method, _ ->
method.name == "invoke"
}
} }
internal const val PLAYLIST_BACKGROUND_COLOR_LITERAL = 0xFF121212 internal val convertArgbToRgbaFingerprint = fingerprint {
internal const val SHARE_MENU_BACKGROUND_COLOR_LITERAL = 0xFF1F1F1F accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC, AccessFlags.FINAL)
internal const val HOME_CATEGORY_PILL_COLOR_LITERAL = 0xFF333333 returns("J")
internal const val SETTINGS_HEADER_COLOR_LITERAL = 0xFF282828 parameters("J")
internal val homeCategoryPillColorsFingerprint = fingerprint{
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR)
custom { method, _ ->
method.containsLiteralInstruction(HOME_CATEGORY_PILL_COLOR_LITERAL) &&
method.containsLiteralInstruction(0x33000000)
}
} }
internal val settingsHeaderColorFingerprint = fingerprint { internal val parseLottieJsonFingerprint = fingerprint {
accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) strings("Unsupported matte type: ")
}
internal val parseAnimatedColorFingerprint = fingerprint {
parameters("L", "F")
returns("Ljava/lang/Object;")
custom { method, _ -> custom { method, _ ->
method.containsLiteralInstruction(SETTINGS_HEADER_COLOR_LITERAL) && method.containsLiteralInstruction(255.0) &&
method.containsLiteralInstruction(0) method.containsLiteralInstruction(1.0)
} }
} }

View File

@@ -3,9 +3,9 @@ package app.revanced.patches.spotify.lite.ondemand
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
@Deprecated("Patch no longer works and will be deleted soon")
@Suppress("unused") @Suppress("unused")
val onDemandPatch = bytecodePatch( val onDemandPatch = bytecodePatch(
name = "Enable on demand",
description = "Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads.", description = "Enables listening to songs on-demand, allowing to play any song from playlists, albums or artists without limitations. This does not remove ads.",
) { ) {
compatibleWith("com.spotify.lite") compatibleWith("com.spotify.lite")

View File

@@ -1,15 +1,18 @@
package app.revanced.patches.spotify.misc package app.revanced.patches.spotify.misc
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.TypeReference import com.android.tools.smali.dexlib2.iface.reference.TypeReference
internal val accountAttributeFingerprint = fingerprint { context(BytecodePatchContext)
internal val accountAttributeFingerprint get() = fingerprint {
custom { _, classDef -> custom { _, classDef ->
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) { classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
"Lcom/spotify/useraccount/v1/AccountAttribute;" "Lcom/spotify/useraccount/v1/AccountAttribute;"
@@ -19,7 +22,8 @@ internal val accountAttributeFingerprint = fingerprint {
} }
} }
internal val productStateProtoGetMapFingerprint = fingerprint { context(BytecodePatchContext)
internal val productStateProtoGetMapFingerprint get() = fingerprint {
returns("Ljava/util/Map;") returns("Ljava/util/Map;")
custom { _, classDef -> custom { _, classDef ->
classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) { classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) {
@@ -34,9 +38,22 @@ internal val buildQueryParametersFingerprint = fingerprint {
strings("trackRows", "device_type:tablet") strings("trackRows", "device_type:tablet")
} }
internal val contextMenuExperimentsFingerprint = fingerprint { internal val contextMenuViewModelClassFingerprint = fingerprint {
strings("ContextMenuViewModel(header=")
}
internal val contextMenuViewModelAddItemFingerprint = fingerprint {
parameters("L") parameters("L")
strings("remove_ads_upsell_enabled") returns("V")
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<MethodReference>()?.name == "add"
} >= 0
}
}
internal val getViewModelFingerprint = fingerprint {
custom { method, _ -> method.name == "getViewModel" }
} }
internal val contextFromJsonFingerprint = fingerprint { internal val contextFromJsonFingerprint = fingerprint {
@@ -47,15 +64,15 @@ internal val contextFromJsonFingerprint = fingerprint {
Opcode.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC Opcode.INVOKE_STATIC
) )
custom { methodDef, classDef -> custom { method, classDef ->
methodDef.name == "fromJson" && method.name == "fromJson" &&
classDef.endsWith("voiceassistants/playermodels/ContextJsonAdapter;") classDef.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
} }
} }
internal val readPlayerOptionOverridesFingerprint = fingerprint { internal val readPlayerOptionOverridesFingerprint = fingerprint {
custom { methodDef, classDef -> custom { method, classDef ->
methodDef.name == "readPlayerOptionOverrides" && method.name == "readPlayerOptionOverrides" &&
classDef.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;") classDef.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
} }
} }
@@ -65,8 +82,15 @@ internal val protobufListsFingerprint = fingerprint {
custom { method, _ -> method.name == "emptyProtobufList" } custom { method, _ -> method.name == "emptyProtobufList" }
} }
internal val protobufListRemoveFingerprint = fingerprint { internal val abstractProtobufListEnsureIsMutableFingerprint = fingerprint {
custom { method, _ -> method.name == "remove" } accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
parameters()
returns("V")
custom { method, _ ->
method.indexOfFirstInstruction {
getReference<TypeReference>()?.type == "Ljava/lang/UnsupportedOperationException;"
} >= 0
}
} }
internal val homeSectionFingerprint = fingerprint { internal val homeSectionFingerprint = fingerprint {
@@ -84,7 +108,8 @@ internal val homeStructureGetSectionsFingerprint = fingerprint {
internal fun reactivexFunctionApplyWithClassInitFingerprint(className: String) = fingerprint { internal fun reactivexFunctionApplyWithClassInitFingerprint(className: String) = fingerprint {
returns("Ljava/lang/Object;") returns("Ljava/lang/Object;")
parameters("Ljava/lang/Object;") parameters("Ljava/lang/Object;")
custom { method, _ -> method.name == "apply" && method.indexOfFirstInstruction { custom { method, _ ->
method.name == "apply" && method.indexOfFirstInstruction {
opcode == Opcode.NEW_INSTANCE && getReference<TypeReference>()?.type?.endsWith(className) == true opcode == Opcode.NEW_INSTANCE && getReference<TypeReference>()?.type?.endsWith(className) == true
} >= 0 } >= 0
} }

View File

@@ -1,21 +1,5 @@
package app.revanced.patches.spotify.misc.extension package app.revanced.patches.spotify.misc.extension
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.shared.misc.extension.sharedExtensionPatch import app.revanced.patches.shared.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.SPOTIFY_MAIN_ACTIVITY_LEGACY
/** val sharedExtensionPatch = sharedExtensionPatch("spotify", mainActivityOnCreateHook)
* If patching a legacy 8.x target. This may also be set if patching slightly older/newer app targets,
* but the only legacy target of interest is 8.6.98.900 as it's the last version that
* supports Spotify integration on Kenwood/Pioneer car stereos.
*/
internal var IS_SPOTIFY_LEGACY_APP_TARGET = false
val sharedExtensionPatch = bytecodePatch {
dependsOn(sharedExtensionPatch("spotify", mainActivityOnCreateHook))
execute {
IS_SPOTIFY_LEGACY_APP_TARGET = mainActivityOnCreateHook.fingerprint
.originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY
}
}

View File

@@ -15,7 +15,7 @@ val fixFacebookLoginPatch = bytecodePatch(
// The Facebook SDK tries to handle the login using the Facebook app in case it is installed. // The Facebook SDK tries to handle the login using the Facebook app in case it is installed.
// However, the Facebook app does signature checks with the app that is requesting the authentication, // However, the Facebook app does signature checks with the app that is requesting the authentication,
// which ends up making the Facebook server reject with an invalid key hash for the app signature. // which ends up making the Facebook server reject with an invalid key hash for the app signature.
// Override the Faceboook SDK to always handle the login using the web browser, which does not perform // Override the Facebook SDK to always handle the login using the web browser, which does not perform
// signature checks. // signature checks.
val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassFingerprint.originalClassDef val katanaProxyLoginMethodHandlerClass = katanaProxyLoginMethodHandlerClassFingerprint.originalClassDef

View File

@@ -4,8 +4,8 @@ import app.revanced.patcher.Fingerprint
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
@@ -37,13 +37,13 @@ val sanitizeSharingLinksPatch = bytecodePatch(
val newPlainTextInvokeIndex = indexOfFirstInstructionOrThrow { val newPlainTextInvokeIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "newPlainText" getReference<MethodReference>()?.name == "newPlainText"
} }
val register = getInstruction<FiveRegisterInstruction>(newPlainTextInvokeIndex).registerD val urlRegister = getInstruction<FiveRegisterInstruction>(newPlainTextInvokeIndex).registerD
addInstructions( addInstructions(
newPlainTextInvokeIndex, newPlainTextInvokeIndex,
""" """
invoke-static { v$register }, $extensionMethodDescriptor invoke-static { v$urlRegister }, $extensionMethodDescriptor
move-result-object v$register move-result-object v$urlRegister
""" """
) )
} }

View File

@@ -1,7 +1,6 @@
package app.revanced.patches.spotify.misc.widgets package app.revanced.patches.spotify.misc.widgets
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val canBindAppWidgetPermissionFingerprint = fingerprint { internal val canBindAppWidgetPermissionFingerprint = fingerprint {

View File

@@ -1,7 +1,9 @@
package app.revanced.patches.spotify.misc.widgets package app.revanced.patches.spotify.misc.widgets
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.spotify.shared.IS_SPOTIFY_LEGACY_APP_TARGET
import app.revanced.util.returnEarly import app.revanced.util.returnEarly
import java.util.logging.Logger
@Suppress("unused") @Suppress("unused")
val fixThirdPartyLaunchersWidgets = bytecodePatch( val fixThirdPartyLaunchersWidgets = bytecodePatch(
@@ -11,6 +13,14 @@ val fixThirdPartyLaunchersWidgets = bytecodePatch(
compatibleWith("com.spotify.music") compatibleWith("com.spotify.music")
execute { execute {
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
// The permission check does not exist in legacy versions.
Logger.getLogger(this::class.java.name).warning(
"Legacy app target does not have any third party launcher restrictions. No changes applied."
)
return@execute
}
// Only system app launchers are granted the BIND_APPWIDGET permission. // Only system app launchers are granted the BIND_APPWIDGET permission.
// Override the method that checks for it to always return true, as this permission is not actually required // Override the method that checks for it to always return true, as this permission is not actually required
// for the widgets to work. // for the widgets to work.

View File

@@ -1,6 +1,8 @@
package app.revanced.patches.spotify.shared package app.revanced.patches.spotify.shared
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patches.spotify.misc.extension.mainActivityOnCreateHook
private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;" private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;"
@@ -15,3 +17,18 @@ internal val mainActivityOnCreateFingerprint = fingerprint {
|| classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY) || classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY)
} }
} }
private var isLegacyAppTarget: Boolean? = null
/**
* If patching a legacy 8.x target. This may also be set if patching slightly older/newer app targets,
* but the only legacy target of interest is 8.6.98.900 as it's the last version that
* supports Spotify integration on Kenwood/Pioneer car stereos.
*/
context(BytecodePatchContext)
internal val IS_SPOTIFY_LEGACY_APP_TARGET get(): Boolean {
if (isLegacyAppTarget == null) {
isLegacyAppTarget = mainActivityOnCreateHook.fingerprint.originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY
}
return isLegacyAppTarget!!
}

View File

@@ -48,7 +48,10 @@ private val swipeControlsResourcePatch = resourcePatch {
summaryKey = null, summaryKey = null,
), ),
TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER), TextPreference("revanced_swipe_overlay_background_opacity", inputType = InputType.NUMBER),
TextPreference("revanced_swipe_overlay_progress_color", TextPreference("revanced_swipe_overlay_progress_brightness_color",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS),
TextPreference("revanced_swipe_overlay_progress_volume_color",
tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference", tag = "app.revanced.extension.shared.settings.preference.ColorPickerPreference",
inputType = InputType.TEXT_CAP_CHARACTERS), inputType = InputType.TEXT_CAP_CHARACTERS),
TextPreference("revanced_swipe_text_overlay_size", inputType = InputType.NUMBER), TextPreference("revanced_swipe_text_overlay_size", inputType = InputType.NUMBER),

View File

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

View File

@@ -28,6 +28,8 @@ internal var playerControlPreviousButtonTouchArea = -1L
private set private set
internal var playerControlNextButtonTouchArea = -1L internal var playerControlNextButtonTouchArea = -1L
private set private set
internal var controlsButtonGroupLayoutStub = -1L
private set
private val hidePlayerOverlayButtonsResourcePatch = resourcePatch { private val hidePlayerOverlayButtonsResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch) dependsOn(resourceMappingPatch)
@@ -35,6 +37,7 @@ private val hidePlayerOverlayButtonsResourcePatch = resourcePatch {
execute { execute {
playerControlPreviousButtonTouchArea = resourceMappings["id", "player_control_previous_button_touch_area"] playerControlPreviousButtonTouchArea = resourceMappings["id", "player_control_previous_button_touch_area"]
playerControlNextButtonTouchArea = resourceMappings["id", "player_control_next_button_touch_area"] playerControlNextButtonTouchArea = resourceMappings["id", "player_control_next_button_touch_area"]
controlsButtonGroupLayoutStub = resourceMappings["id", "youtube_controls_button_group_layout_stub"]
} }
} }
@@ -43,7 +46,8 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
val hidePlayerOverlayButtonsPatch = bytecodePatch( val hidePlayerOverlayButtonsPatch = bytecodePatch(
name = "Hide player overlay buttons", name = "Hide player overlay buttons",
description = "Adds options to hide the player Cast, Autoplay, Captions, and Previous & Next buttons.", description = "Adds options to hide the player Cast, Autoplay, Captions, Previous & Next buttons, and the player " +
"control buttons background.",
) { ) {
dependsOn( dependsOn(
sharedExtensionPatch, sharedExtensionPatch,
@@ -72,6 +76,7 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
SwitchPreference("revanced_hide_cast_button"), SwitchPreference("revanced_hide_cast_button"),
SwitchPreference("revanced_hide_captions_button"), SwitchPreference("revanced_hide_captions_button"),
SwitchPreference("revanced_hide_autoplay_button"), SwitchPreference("revanced_hide_autoplay_button"),
SwitchPreference("revanced_hide_player_control_buttons_background"),
) )
// region Hide player next/previous button. // region Hide player next/previous button.
@@ -147,5 +152,32 @@ val hidePlayerOverlayButtonsPatch = bytecodePatch(
} }
// endregion // endregion
// region Hide player control buttons background.
inflateControlsGroupLayoutStubFingerprint.method.apply {
val controlsButtonGroupLayoutStubResIdConstIndex =
indexOfFirstLiteralInstructionOrThrow(controlsButtonGroupLayoutStub)
val inflateControlsGroupLayoutStubIndex =
indexOfFirstInstruction(controlsButtonGroupLayoutStubResIdConstIndex) {
getReference<MethodReference>()?.name == "inflate"
}
val freeRegister = findFreeRegister(inflateControlsGroupLayoutStubIndex)
val hidePlayerControlButtonsBackgroundDescriptor =
"$EXTENSION_CLASS_DESCRIPTOR->hidePlayerControlButtonsBackground(Landroid/view/View;)V"
addInstructions(
inflateControlsGroupLayoutStubIndex + 1,
"""
# Move the inflated layout to a temporary register.
# The result of the inflate method is by default not moved to a register after the method is called.
move-result-object v$freeRegister
invoke-static { v$freeRegister }, $hidePlayerControlButtonsBackgroundDescriptor
"""
)
}
// endregion
} }
} }

View File

@@ -161,7 +161,7 @@ val hideLayoutComponentsPatch = bytecodePatch(
SwitchPreference("revanced_hide_comments_by_members_header"), SwitchPreference("revanced_hide_comments_by_members_header"),
SwitchPreference("revanced_hide_comments_section"), SwitchPreference("revanced_hide_comments_section"),
SwitchPreference("revanced_hide_comments_create_a_short_button"), SwitchPreference("revanced_hide_comments_create_a_short_button"),
SwitchPreference("revanced_hide_comments_timestamp_and_emoji_buttons"), SwitchPreference("revanced_hide_comments_timestamp_button"),
SwitchPreference("revanced_hide_comments_preview_comment"), SwitchPreference("revanced_hide_comments_preview_comment"),
SwitchPreference("revanced_hide_comments_thanks_button"), SwitchPreference("revanced_hide_comments_thanks_button"),
), ),

View File

@@ -74,3 +74,21 @@ internal val setPivotBarVisibilityParentFingerprint = fingerprint {
parameters("Z") parameters("Z")
strings("FEnotifications_inbox") strings("FEnotifications_inbox")
} }
internal val shortsExperimentalPlayerFeatureFlagFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Z")
parameters()
literal {
45677719L
}
}
internal val renderNextUIFeatureFlagFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("Z")
parameters()
literal {
45649743L
}
}

View File

@@ -18,6 +18,7 @@ import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter
import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_41_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_41_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
@@ -26,6 +27,7 @@ import app.revanced.util.forEachLiteralValueInstruction
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstruction import app.revanced.util.indexOfFirstLiteralInstruction
import app.revanced.util.returnLate
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@@ -90,11 +92,13 @@ private val hideShortsComponentsResourcePatch = resourcePatch {
SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"), SwitchPreference("revanced_hide_shorts_paused_overlay_buttons"),
// Suggested actions. // Suggested actions.
SwitchPreference("revanced_hide_shorts_preview_comment"),
SwitchPreference("revanced_hide_shorts_save_sound_button"), SwitchPreference("revanced_hide_shorts_save_sound_button"),
SwitchPreference("revanced_hide_shorts_use_template_button"), SwitchPreference("revanced_hide_shorts_use_template_button"),
SwitchPreference("revanced_hide_shorts_upcoming_button"), SwitchPreference("revanced_hide_shorts_upcoming_button"),
SwitchPreference("revanced_hide_shorts_green_screen_button"), SwitchPreference("revanced_hide_shorts_green_screen_button"),
SwitchPreference("revanced_hide_shorts_hashtag_button"), SwitchPreference("revanced_hide_shorts_hashtag_button"),
SwitchPreference("revanced_hide_shorts_new_posts_button"),
SwitchPreference("revanced_hide_shorts_shop_button"), SwitchPreference("revanced_hide_shorts_shop_button"),
SwitchPreference("revanced_hide_shorts_tagged_products"), SwitchPreference("revanced_hide_shorts_tagged_products"),
SwitchPreference("revanced_hide_shorts_search_suggestions"), SwitchPreference("revanced_hide_shorts_search_suggestions"),
@@ -251,5 +255,27 @@ val hideShortsComponentsPatch = bytecodePatch(
} }
// endregion // endregion
// region Disable experimental Shorts flags.
// Flags might be present in earlier targets, but they are not found in 19.47.53.
// If these flags are forced on, the experimental layout is still not used and
// it appears the features requires additional server side data to fully use.
if (is_20_07_or_greater) {
// Experimental Shorts player uses Android native buttons and not Litho,
// and the layout is provided by the server.
//
// Since the buttons are native components and not Litho, it should be possible to
// fix the RYD Shorts loading delay by asynchronously loading RYD and updating
// the button text after RYD has loaded.
shortsExperimentalPlayerFeatureFlagFingerprint.method.returnLate(false)
// Experimental UI renderer must also be disabled since it requires the
// experimental Shorts player. If this is enabled but Shorts player
// is disabled then the app crashes when the Shorts player is opened.
renderNextUIFeatureFlagFingerprint.method.returnLate(false)
}
// endregion
} }
} }

View File

@@ -1,37 +1,12 @@
package app.revanced.patches.youtube.layout.player.background package app.revanced.patches.youtube.layout.player.background
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.doRecursively import app.revanced.patches.youtube.layout.buttons.overlay.hidePlayerOverlayButtonsPatch
import org.w3c.dom.Element
@Suppress("unused") @Suppress("unused")
@Deprecated("Functionality added to hidePlayerOverlayButtonsPatch", ReplaceWith("hidePlayerOverlayButtonsPatch"))
val playerControlsBackgroundPatch = resourcePatch( val playerControlsBackgroundPatch = resourcePatch(
name = "Remove player controls background", description = "Removes the dark background surrounding the video player control buttons.",
description = "Removes the dark background surrounding the video player controls.",
use = false,
) { ) {
compatibleWith( dependsOn(hidePlayerOverlayButtonsPatch)
"com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
)
)
execute {
document("res/drawable/player_button_circle_background.xml").use { document ->
document.doRecursively node@{ node ->
if (node !is Element) return@node
node.getAttributeNode("android:color")?.let { attribute ->
attribute.textContent = "@android:color/transparent"
}
}
}
}
} }

View File

@@ -44,6 +44,19 @@ internal val themeHelperLightColorFingerprint = fingerprint {
} }
} }
internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L
internal val useGradientLoadingScreenFingerprint = fingerprint { internal val useGradientLoadingScreenFingerprint = fingerprint {
literal { GRADIENT_LOADING_SCREEN_AB_CONSTANT } literal { GRADIENT_LOADING_SCREEN_AB_CONSTANT }
} }
internal const val SPLASH_SCREEN_STYLE_FEATURE_FLAG = 269032877L
internal val splashScreenStyleFingerprint = fingerprint {
returns("V")
parameters("Landroid/os/Bundle;")
literal { SPLASH_SCREEN_STYLE_FEATURE_FLAG }
custom { method, classDef ->
method.name == "onCreate" && classDef.endsWith("/MainActivity;")
}
}

View File

@@ -10,6 +10,7 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.BasePreference import app.revanced.patches.shared.misc.settings.preference.BasePreference
import app.revanced.patches.shared.misc.settings.preference.InputType import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.ListPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
@@ -17,6 +18,7 @@ import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.layout.seekbar.seekbarColorPatch import app.revanced.patches.youtube.layout.seekbar.seekbarColorPatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_19_47_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
@@ -27,8 +29,6 @@ import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR = private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/theme/ThemePatch;" "Lapp/revanced/extension/youtube/patches/theme/ThemePatch;"
internal const val GRADIENT_LOADING_SCREEN_AB_CONSTANT = 45412406L
val themePatch = bytecodePatch( val themePatch = bytecodePatch(
name = "Theme", name = "Theme",
description = "Adds options for theming and applies a custom background theme (dark background theme defaults to amoled black).", description = "Adds options for theming and applies a custom background theme (dark background theme defaults to amoled black).",
@@ -232,15 +232,32 @@ val themePatch = bytecodePatch(
addResources("youtube", "layout.theme.themePatch") addResources("youtube", "layout.theme.themePatch")
PreferenceScreen.GENERAL_LAYOUT.addPreferences( PreferenceScreen.GENERAL_LAYOUT.addPreferences(
SwitchPreference("revanced_gradient_loading_screen"), SwitchPreference("revanced_gradient_loading_screen")
) )
if (is_19_47_or_greater) {
PreferenceScreen.GENERAL_LAYOUT.addPreferences(
ListPreference(
key = "splash_screen_animation_style",
summaryKey = null
)
)
}
useGradientLoadingScreenFingerprint.method.insertLiteralOverride( useGradientLoadingScreenFingerprint.method.insertLiteralOverride(
GRADIENT_LOADING_SCREEN_AB_CONSTANT, GRADIENT_LOADING_SCREEN_AB_CONSTANT,
"$EXTENSION_CLASS_DESCRIPTOR->gradientLoadingScreenEnabled(Z)Z" "$EXTENSION_CLASS_DESCRIPTOR->gradientLoadingScreenEnabled(Z)Z"
) )
mapOf( if (is_19_47_or_greater) {
// Lottie splash screen exists in earlier versions, but it may not be always on.
splashScreenStyleFingerprint.method.insertLiteralOverride(
SPLASH_SCREEN_STYLE_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->getLoadingScreenType(I)I"
)
}
arrayOf(
themeHelperLightColorFingerprint to lightThemeBackgroundColor, themeHelperLightColorFingerprint to lightThemeBackgroundColor,
themeHelperDarkColorFingerprint to darkThemeBackgroundColor, themeHelperDarkColorFingerprint to darkThemeBackgroundColor,
).forEach { (fingerprint, color) -> ).forEach { (fingerprint, color) ->

View File

@@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
@@ -23,7 +24,7 @@ private const val EXTENSION_CLASS_DESCRIPTOR =
val enableDebuggingPatch = bytecodePatch( val enableDebuggingPatch = bytecodePatch(
name = "Enable debugging", name = "Enable debugging",
description = "Adds options for debugging.", description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
) { ) {
dependsOn( dependsOn(
sharedExtensionPatch, sharedExtensionPatch,
@@ -56,6 +57,16 @@ val enableDebuggingPatch = bytecodePatch(
SwitchPreference("revanced_debug_protobuffer"), SwitchPreference("revanced_debug_protobuffer"),
SwitchPreference("revanced_debug_stacktrace"), SwitchPreference("revanced_debug_stacktrace"),
SwitchPreference("revanced_debug_toast_on_error"), SwitchPreference("revanced_debug_toast_on_error"),
NonInteractivePreference(
"revanced_debug_export_logs_to_clipboard",
tag = "app.revanced.extension.youtube.settings.preference.ExportLogToClipboardPreference",
selectable = true,
),
NonInteractivePreference(
"revanced_debug_logs_clear_buffer",
tag = "app.revanced.extension.youtube.settings.preference.ClearLogBufferPreference",
selectable = true,
),
), ),
), ),
) )
@@ -107,7 +118,6 @@ val enableDebuggingPatch = bytecodePatch(
return-wide v0 return-wide v0
""" """
) )
} }
experimentalStringFeatureFlagFingerprint.match( experimentalStringFeatureFlagFingerprint.match(

View File

@@ -0,0 +1,74 @@
package app.revanced.patches.youtube.misc.hapticfeedback
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
private const val EXTENSION_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/DisableHapticFeedbackPatch;"
@Suppress("unused")
val disableHapticFeedbackPatch = bytecodePatch(
name = "Disable haptic feedback",
description = "Adds an option to disable haptic feedback in the player for various actions.",
) {
dependsOn(
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
)
)
execute {
addResources("youtube", "misc.hapticfeedback.disableHapticFeedbackPatch")
PreferenceScreen.PLAYER.addPreferences(
PreferenceScreenPreference(
"revanced_disable_haptic_feedback",
preferences = setOf(
SwitchPreference("revanced_disable_haptic_feedback_chapters"),
SwitchPreference("revanced_disable_haptic_feedback_precise_seeking"),
SwitchPreference("revanced_disable_haptic_feedback_seek_undo"),
SwitchPreference("revanced_disable_haptic_feedback_zoom"),
)
)
)
arrayOf(
markerHapticsFingerprint to "disableChapterVibrate",
scrubbingHapticsFingerprint to "disablePreciseSeekingVibrate",
seekUndoHapticsFingerprint to "disableSeekUndoVibrate",
zoomHapticsFingerprint to "disableZoomVibrate"
).forEach { (fingerprint, methodName) ->
fingerprint.method.apply {
addInstructionsWithLabels(
0,
"""
invoke-static {}, $EXTENSION_CLASS_DESCRIPTOR->$methodName()Z
move-result v0
if-eqz v0, :vibrate
return-void
""",
ExternalLabel("vibrate", getInstruction(0))
)
}
}
}
}

View File

@@ -0,0 +1,23 @@
package app.revanced.patches.youtube.misc.hapticfeedback
import app.revanced.patcher.fingerprint
internal val markerHapticsFingerprint = fingerprint {
returns("V")
strings("Failed to execute markers haptics vibrate.")
}
internal val scrubbingHapticsFingerprint = fingerprint {
returns("V")
strings("Failed to haptics vibrate for fine scrubbing.")
}
internal val seekUndoHapticsFingerprint = fingerprint {
returns("V")
strings("Failed to execute seek undo haptics vibrate.")
}
internal val zoomHapticsFingerprint = fingerprint {
returns("V")
strings("Failed to haptics vibrate for video zoom")
}

View File

@@ -1,7 +0,0 @@
package app.revanced.patches.youtube.misc.zoomhaptics
import app.revanced.patcher.fingerprint
internal val zoomHapticsFingerprint = fingerprint {
strings("Failed to haptics vibrate for video zoom")
}

View File

@@ -1,54 +1,11 @@
package app.revanced.patches.youtube.misc.zoomhaptics package app.revanced.patches.youtube.misc.zoomhaptics
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.youtube.misc.hapticfeedback.disableHapticFeedbackPatch
import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch
@Deprecated("Superseded by disableHapticFeedbackPatch", ReplaceWith("disableHapticFeedbackPatch"))
val zoomHapticsPatch = bytecodePatch( val zoomHapticsPatch = bytecodePatch(
name = "Disable zoom haptics",
description = "Adds an option to disable haptics when zooming.", description = "Adds an option to disable haptics when zooming.",
) { ) {
dependsOn( dependsOn(disableHapticFeedbackPatch)
settingsPatch,
addResourcesPatch,
)
compatibleWith(
"com.google.android.youtube"(
"19.16.39",
"19.25.37",
"19.34.42",
"19.43.41",
"19.47.53",
"20.07.39",
"20.12.46",
)
)
execute {
addResources("youtube", "misc.zoomhaptics.zoomHapticsPatch")
PreferenceScreen.MISC.addPreferences(
SwitchPreference("revanced_disable_zoom_haptics"),
)
zoomHapticsFingerprint.method.apply {
addInstructionsWithLabels(
0,
"""
invoke-static { }, Lapp/revanced/extension/youtube/patches/ZoomHapticsPatch;->shouldVibrate()Z
move-result v0
if-nez v0, :vibrate
return-void
""",
ExternalLabel("vibrate", getInstruction(0)),
)
}
}
} }

View File

@@ -123,3 +123,13 @@ internal val playbackSpeedMenuSpeedChangedFingerprint = fingerprint {
Opcode.RETURN_OBJECT, Opcode.RETURN_OBJECT,
) )
} }
internal val playbackSpeedClassFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("L")
parameters("L")
opcodes(
Opcode.RETURN_OBJECT
)
strings("PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT")
}

View File

@@ -7,6 +7,7 @@ import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.toInstructions
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
import app.revanced.patches.youtube.video.playerresponse.Hook import app.revanced.patches.youtube.video.playerresponse.Hook
@@ -16,6 +17,8 @@ import app.revanced.patches.youtube.video.videoid.hookBackgroundPlayVideoId
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
import app.revanced.patches.youtube.video.videoid.hookVideoId import app.revanced.patches.youtube.video.videoid.hookVideoId
import app.revanced.patches.youtube.video.videoid.videoIdPatch import app.revanced.patches.youtube.video.videoid.videoIdPatch
import app.revanced.util.addInstructionsAtControlFlowLabel
import app.revanced.util.addStaticFieldToExtension
import app.revanced.util.getReference import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
@@ -29,6 +32,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
@@ -189,6 +193,72 @@ val videoInformationPatch = bytecodePatch(
proxy(classes.first { it.type == setPlaybackSpeedMethodReference.definingClass }) proxy(classes.first { it.type == setPlaybackSpeedMethodReference.definingClass })
.mutableClass.methods.first { it.name == setPlaybackSpeedMethodReference.name } .mutableClass.methods.first { it.name == setPlaybackSpeedMethodReference.name }
setPlaybackSpeedMethodIndex = 0 setPlaybackSpeedMethodIndex = 0
// Add override playback speed method.
onPlaybackSpeedItemClickFingerprint.classDef.methods.add(
ImmutableMethod(
definingClass,
"overridePlaybackSpeed",
listOf(ImmutableMethodParameter("F", annotations, null)),
"V",
AccessFlags.PUBLIC.value or AccessFlags.PUBLIC.value,
annotations,
null,
ImmutableMethodImplementation(
4,
"""
# Check if the playback speed is not auto (-2.0f)
const/4 v0, 0x0
cmpg-float v0, v3, v0
if-lez v0, :ignore
# Get the container class field.
iget-object v0, v2, $setPlaybackSpeedContainerClassFieldReference
# For some reason, in YouTube 19.44.39 this value is sometimes null.
if-eqz v0, :ignore
# Get the field from its class.
iget-object v1, v0, $setPlaybackSpeedClassFieldReference
# Invoke setPlaybackSpeed on that class.
invoke-virtual {v1, v3}, $setPlaybackSpeedMethodReference
:ignore
return-void
""".toInstructions(), null, null
)
).toMutable()
)
}
playbackSpeedClassFingerprint.method.apply {
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
val register = getInstruction<OneRegisterInstruction>(index).registerA
val playbackSpeedClass = this.returnType
// Set playback speed class.
addInstructionsAtControlFlowLabel(
index,
"sput-object v$register, $EXTENSION_CLASS_DESCRIPTOR->playbackSpeedClass:$playbackSpeedClass"
)
val smaliInstructions =
"""
if-eqz v0, :ignore
invoke-virtual {v0, p0}, $playbackSpeedClass->overridePlaybackSpeed(F)V
return-void
:ignore
nop
"""
addStaticFieldToExtension(
EXTENSION_CLASS_DESCRIPTOR,
"overridePlaybackSpeed",
"playbackSpeedClass",
playbackSpeedClass,
smaliInstructions
)
} }
// Handle new playback speed menu. // Handle new playback speed menu.

View File

@@ -52,14 +52,14 @@ val rememberVideoQualityPatch = bytecodePatch {
ListPreference( ListPreference(
key = "revanced_shorts_quality_default_mobile", key = "revanced_shorts_quality_default_mobile",
summaryKey = null, summaryKey = null,
entriesKey = "revanced_video_quality_default_entries", entriesKey = "revanced_shorts_quality_default_entries",
entryValuesKey = "revanced_video_quality_default_entry_values", entryValuesKey = "revanced_shorts_quality_default_entry_values",
), ),
ListPreference( ListPreference(
key = "revanced_shorts_quality_default_wifi", key = "revanced_shorts_quality_default_wifi",
summaryKey = null, summaryKey = null,
entriesKey = "revanced_video_quality_default_entries", entriesKey = "revanced_shorts_quality_default_entries",
entryValuesKey = "revanced_video_quality_default_entry_values", entryValuesKey = "revanced_shorts_quality_default_entry_values",
), ),
SwitchPreference("revanced_remember_shorts_quality_last_selected") SwitchPreference("revanced_remember_shorts_quality_last_selected")
)) ))

View File

@@ -1,19 +1,11 @@
package app.revanced.patches.youtube.video.speed.custom package app.revanced.patches.youtube.video.speed.custom
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.InputType import app.revanced.patches.shared.misc.settings.preference.InputType
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference
@@ -27,27 +19,11 @@ import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTr
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
import app.revanced.util.* import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.AccessFlags import app.revanced.util.indexOfFirstLiteralInstruction
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableField
internal var speedUnavailableId = -1L
private set
private val customPlaybackSpeedResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch)
execute {
speedUnavailableId = resourceMappings[
"string",
"varispeed_unavailable_message",
]
}
}
private const val FILTER_CLASS_DESCRIPTOR = private const val FILTER_CLASS_DESCRIPTOR =
"Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilterPatch;" "Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilterPatch;"
@@ -64,8 +40,7 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
addResourcesPatch, addResourcesPatch,
lithoFilterPatch, lithoFilterPatch,
versionCheckPatch, versionCheckPatch,
recyclerViewTreeHookPatch, recyclerViewTreeHookPatch
customPlaybackSpeedResourcePatch
) )
execute { execute {
@@ -87,38 +62,6 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
) )
} }
// Replace the speeds float array with custom speeds.
speedArrayGeneratorFingerprint.method.apply {
val sizeCallIndex = indexOfFirstInstructionOrThrow { getReference<MethodReference>()?.name == "size" }
val sizeCallResultRegister = getInstruction<OneRegisterInstruction>(sizeCallIndex + 1).registerA
replaceInstruction(sizeCallIndex + 1, "const/4 v$sizeCallResultRegister, 0x0")
val arrayLengthConstIndex = indexOfFirstLiteralInstructionOrThrow(7)
val arrayLengthConstDestination = getInstruction<OneRegisterInstruction>(arrayLengthConstIndex).registerA
val playbackSpeedsArrayType = "$EXTENSION_CLASS_DESCRIPTOR->customPlaybackSpeeds:[F"
addInstructions(
arrayLengthConstIndex + 1,
"""
sget-object v$arrayLengthConstDestination, $playbackSpeedsArrayType
array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
""",
)
val originalArrayFetchIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<FieldReference>()
reference?.type == "[F" && reference.definingClass.endsWith("/PlayerConfigModel;")
}
val originalArrayFetchDestination =
getInstruction<OneRegisterInstruction>(originalArrayFetchIndex).registerA
replaceInstruction(
originalArrayFetchIndex,
"sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType",
)
}
// Override the min/max speeds that can be used. // Override the min/max speeds that can be used.
speedLimiterFingerprint.method.apply { speedLimiterFingerprint.method.apply {
val limitMinIndex = indexOfFirstLiteralInstructionOrThrow(0.25f) val limitMinIndex = indexOfFirstLiteralInstructionOrThrow(0.25f)
@@ -135,47 +78,7 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
replaceInstruction(limitMaxIndex, "const/high16 v$limitMaxRegister, 8.0f") replaceInstruction(limitMaxIndex, "const/high16 v$limitMaxRegister, 8.0f")
} }
// Add a static INSTANCE field to the class. // Close the unpatched playback dialog and show the modern custom dialog.
// This is later used to call "showOldPlaybackSpeedMenu" on the instance.
val instanceField = ImmutableField(
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
"INSTANCE",
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
AccessFlags.PUBLIC.value or AccessFlags.STATIC.value,
null,
null,
null,
).toMutable()
getOldPlaybackSpeedsFingerprint.classDef.staticFields.add(instanceField)
// Set the INSTANCE field to the instance of the class.
// In order to prevent a conflict with another patch, add the instruction at index 1.
getOldPlaybackSpeedsFingerprint.method.addInstruction(1, "sput-object p0, $instanceField")
// Get the "showOldPlaybackSpeedMenu" method.
// This is later called on the field INSTANCE.
val showOldPlaybackSpeedMenuMethod = showOldPlaybackSpeedMenuFingerprint.match(
getOldPlaybackSpeedsFingerprint.classDef,
).method.toString()
// Insert the call to the "showOldPlaybackSpeedMenu" method on the field INSTANCE.
showOldPlaybackSpeedMenuExtensionFingerprint.method.apply {
addInstructionsWithLabels(
instructions.lastIndex,
"""
sget-object v0, $instanceField
if-nez v0, :not_null
return-void
:not_null
invoke-virtual { v0 }, $showOldPlaybackSpeedMenuMethod
""",
)
}
// region Force old video quality menu.
// This is necessary, because there is no known way of adding custom playback speeds to the new menu.
addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR) addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR)
// Required to check if the playback speed menu is currently shown. // Required to check if the playback speed menu is currently shown.

View File

@@ -1,30 +1,9 @@
package app.revanced.patches.youtube.video.speed.custom package app.revanced.patches.youtube.video.speed.custom
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
internal val getOldPlaybackSpeedsFingerprint = fingerprint {
parameters("[L", "I")
strings("menu_item_playback_speed")
}
internal val showOldPlaybackSpeedMenuFingerprint = fingerprint {
literal { speedUnavailableId }
}
internal val showOldPlaybackSpeedMenuExtensionFingerprint = fingerprint {
custom { method, _ -> method.name == "showOldPlaybackSpeedMenu" }
}
internal val speedArrayGeneratorFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
returns("[L")
parameters("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;")
strings("0.0#")
}
internal val speedLimiterFingerprint = fingerprint { internal val speedLimiterFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V") returns("V")

View File

@@ -10,6 +10,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.get
@@ -31,6 +32,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstructio
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.reference.Reference import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.util.MethodUtil import com.android.tools.smali.dexlib2.util.MethodUtil
import java.util.EnumSet import java.util.EnumSet
@@ -962,6 +964,43 @@ private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean
} }
} }
internal fun BytecodePatchContext.addStaticFieldToExtension(
className: String,
methodName: String,
fieldName: String,
objectClass: String,
smaliInstructions: String
) {
val classDef = classes.find { classDef -> classDef.type == className }
?: throw PatchException("No matching methods found in: $className")
val mutableClass = proxy(classDef).mutableClass
val objectCall = "$mutableClass->$fieldName:$objectClass"
mutableClass.apply {
methods.first { method -> method.name == methodName }.apply {
staticFields.add(
ImmutableField(
definingClass,
fieldName,
objectClass,
AccessFlags.PUBLIC.value or AccessFlags.STATIC.value,
null,
annotations,
null
).toMutable()
)
addInstructionsWithLabels(
0,
"""
sget-object v0, $objectCall
""" + smaliInstructions
)
}
}
}
/** /**
* Set the custom condition for this fingerprint to check for a literal value. * Set the custom condition for this fingerprint to check for a literal value.
* *

View File

@@ -199,6 +199,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
</patch> </patch>
<patch id="misc.links.bypassURLRedirectsPatch"> <patch id="misc.links.bypassURLRedirectsPatch">
@@ -207,8 +209,6 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.privacy.removeTrackingQueryParameterPatch"> <patch id="misc.privacy.removeTrackingQueryParameterPatch">
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title --> <!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title -->
</patch> </patch>

View File

@@ -199,6 +199,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
</patch> </patch>
<patch id="misc.links.bypassURLRedirectsPatch"> <patch id="misc.links.bypassURLRedirectsPatch">
@@ -207,8 +209,6 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.privacy.removeTrackingQueryParameterPatch"> <patch id="misc.privacy.removeTrackingQueryParameterPatch">
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title --> <!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title -->
</patch> </patch>

View File

@@ -35,7 +35,7 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">الإعدادات</string> <string name="revanced_settings_submenu_title">الإعدادات</string>
<string name="revanced_settings_confirm_user_dialog_title">هل ترغب في المتابعة؟</string> <string name="revanced_settings_confirm_user_dialog_title">هل ترغب في المتابعة؟</string>
<string name="revanced_settings_reset">إعادة التعيين</string> <string name="revanced_settings_reset">إعادة التعيين</string>
<string name="revanced_settings_reset_color">إعادة تعيين اللون</string> <string name="revanced_settings_reset_color">Reset color</string>
<string name="revanced_settings_color_invalid">لون غير صالح</string> <string name="revanced_settings_color_invalid">لون غير صالح</string>
<string name="revanced_settings_restart_title">تحديث وإعادة التشغيل</string> <string name="revanced_settings_restart_title">تحديث وإعادة التشغيل</string>
<string name="revanced_settings_restart">إعادة التشغيل</string> <string name="revanced_settings_restart">إعادة التشغيل</string>
@@ -117,6 +117,11 @@ Second \"item\" text"</string>
<string name="revanced_debug_protobuffer_title">سجل بروتوكول التخزين المؤقت</string> <string name="revanced_debug_protobuffer_title">سجل بروتوكول التخزين المؤقت</string>
<string name="revanced_debug_protobuffer_summary_on">تتضمن سجلات التصحيح التخزين المؤقت</string> <string name="revanced_debug_protobuffer_summary_on">تتضمن سجلات التصحيح التخزين المؤقت</string>
<string name="revanced_debug_protobuffer_summary_off">لا تتضمن سجلات التصحيح التخزين المؤقت</string> <string name="revanced_debug_protobuffer_summary_off">لا تتضمن سجلات التصحيح التخزين المؤقت</string>
<string name="revanced_debug_protobuffer_user_dialog_message">"سيؤدي تمكين هذا الإعداد إلى تسجيل بيانات تخطيط إضافية، بما في ذلك النص المعروض على الشاشة لبعض مكونات واجهة المستخدم.
يمكن أن يساعد هذا في تحديد المكونات عند إنشاء عوامل تصفية مخصصة.
ومع ذلك، سيؤدي تمكين هذا أيضًا إلى تسجيل بعض بيانات المستخدم مثل عنوان IP الخاص بك."</string>
<string name="revanced_debug_stacktrace_title">سجل تتبع المكدس</string> <string name="revanced_debug_stacktrace_title">سجل تتبع المكدس</string>
<string name="revanced_debug_stacktrace_summary_on">تتضمن سجلات التصحيح سجل تتبع المكدس</string> <string name="revanced_debug_stacktrace_summary_on">تتضمن سجلات التصحيح سجل تتبع المكدس</string>
<string name="revanced_debug_stacktrace_summary_off">لا تتضمن سجلات التصحيح سجل تتبع المكدس</string> <string name="revanced_debug_stacktrace_summary_off">لا تتضمن سجلات التصحيح سجل تتبع المكدس</string>
@@ -126,6 +131,15 @@ Second \"item\" text"</string>
<string name="revanced_debug_toast_on_error_user_dialog_message">"يؤدي إيقاف تشغيل ملاحظات الأخطاء إلى إخفاء كافة إشعارات أخطاء ReVanced. <string name="revanced_debug_toast_on_error_user_dialog_message">"يؤدي إيقاف تشغيل ملاحظات الأخطاء إلى إخفاء كافة إشعارات أخطاء ReVanced.
لن يتم إعلامك بأي أخطاء غير متوقعة."</string> لن يتم إعلامك بأي أخطاء غير متوقعة."</string>
<string name="revanced_debug_export_logs_to_clipboard_title">تصدير سجلات تصحيح الأخطاء</string>
<string name="revanced_debug_export_logs_to_clipboard_summary">نسخ سجلات تصحيح أخطاء ReVanced إلى الحافظة</string>
<string name="revanced_debug_logs_disabled">تم تعطيل تسجيلات تصحيح الأخطاء</string>
<string name="revanced_debug_logs_none_found">لم يتم العثور على سجلات</string>
<string name="revanced_debug_logs_copied_to_clipboard">تم نسخ السجلات</string>
<string name="revanced_debug_logs_failed_to_export">فشل تصدير السجلات: $s</string>
<string name="revanced_debug_logs_clear_buffer_title">مسح سجلات تصحيح الأخطاء</string>
<string name="revanced_debug_logs_clear_buffer_summary">يمسح جميع سجلات تصحيح أخطاء ReVanced المخزنة</string>
<string name="revanced_debug_logs_clear_toast">تم مسح السجلات</string>
</patch> </patch>
<patch id="layout.hide.general.hideLayoutComponentsPatch"> <patch id="layout.hide.general.hideLayoutComponentsPatch">
<string name="revanced_hide_album_cards_title">إخفاء بطاقات الألبوم</string> <string name="revanced_hide_album_cards_title">إخفاء بطاقات الألبوم</string>
@@ -297,9 +311,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_comments_create_a_short_button_title">إخفاء زر \'إنشاء Short\'</string> <string name="revanced_hide_comments_create_a_short_button_title">إخفاء زر \'إنشاء Short\'</string>
<string name="revanced_hide_comments_create_a_short_button_summary_on">تم إخفاء زر إنشاء Short</string> <string name="revanced_hide_comments_create_a_short_button_summary_on">تم إخفاء زر إنشاء Short</string>
<string name="revanced_hide_comments_create_a_short_button_summary_off">يتم عرض زر إنشاء Short</string> <string name="revanced_hide_comments_create_a_short_button_summary_off">يتم عرض زر إنشاء Short</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_title">إخفاء أزرار الرموز التعبيرية والطوابع الزمنية</string> <string name="revanced_hide_comments_timestamp_button_title">زر إخفاء الطابع الزمني</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_summary_on">تم إخفاء أزرار الرموز التعبيرية والطوابع الزمنية</string> <string name="revanced_hide_comments_timestamp_button_summary_on">زر الطابع الزمني مخفي</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_summary_off">يتم عرض أزرار الرموز التعبيرية والطوابع الزمنية</string> <string name="revanced_hide_comments_timestamp_button_summary_off">زر الطابع الزمني معروض</string>
<string name="revanced_hide_comments_preview_comment_title">إخفاء تعليق المعاينة</string> <string name="revanced_hide_comments_preview_comment_title">إخفاء تعليق المعاينة</string>
<string name="revanced_hide_comments_preview_comment_summary_on">تم إخفاء تعليق المعاينة</string> <string name="revanced_hide_comments_preview_comment_summary_on">تم إخفاء تعليق المعاينة</string>
<string name="revanced_hide_comments_preview_comment_summary_off">يتم عرض تعليق المعاينة</string> <string name="revanced_hide_comments_preview_comment_summary_off">يتم عرض تعليق المعاينة</string>
@@ -477,9 +491,10 @@ Second \"item\" text"</string>
<string name="revanced_swipe_overlay_background_opacity_title">تعتيم خلفية واجهة التمرير السريع</string> <string name="revanced_swipe_overlay_background_opacity_title">تعتيم خلفية واجهة التمرير السريع</string>
<string name="revanced_swipe_overlay_background_opacity_summary">قيمة التعتيم بين 0-100</string> <string name="revanced_swipe_overlay_background_opacity_summary">قيمة التعتيم بين 0-100</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">يجب أن يكون تعتيم التمرير السريع بين 0-100</string> <string name="revanced_swipe_overlay_background_opacity_invalid_toast">يجب أن يكون تعتيم التمرير السريع بين 0-100</string>
<string name="revanced_swipe_overlay_progress_color_title">لون شريط تقدم واجهة التمرير</string> <string name="revanced_swipe_overlay_progress_brightness_color_title">لون سطوع واجهة التمرير</string>
<string name="revanced_swipe_overlay_progress_color_summary">لون شريط التقدم لعناصر التحكم في مستوى الصوت والسطوع</string> <string name="revanced_swipe_overlay_progress_brightness_color_summary">لون شريط التقدم لعناصر التحكم في السطوع</string>
<string name="revanced_swipe_overlay_progress_color_invalid_toast">لون شريط التقدم غير صالح</string> <string name="revanced_swipe_overlay_progress_volume_color_title">لون مستوى صوت واجهة التمرير</string>
<string name="revanced_swipe_overlay_progress_volume_color_summary">لون شريط التقدم لعناصر التحكم في مستوى الصوت</string>
<string name="revanced_swipe_text_overlay_size_title">حجم نص واجهة التمرير</string> <string name="revanced_swipe_text_overlay_size_title">حجم نص واجهة التمرير</string>
<string name="revanced_swipe_text_overlay_size_summary">حجم النص لواجهة التمرير بين 1-30</string> <string name="revanced_swipe_text_overlay_size_summary">حجم النص لواجهة التمرير بين 1-30</string>
<string name="revanced_swipe_text_overlay_size_invalid_toast">يجب أن يكون حجم النص بين 1-30</string> <string name="revanced_swipe_text_overlay_size_invalid_toast">يجب أن يكون حجم النص بين 1-30</string>
@@ -666,6 +681,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_autoplay_button_title">إخفاء زر التشغيل التلقائي</string> <string name="revanced_hide_autoplay_button_title">إخفاء زر التشغيل التلقائي</string>
<string name="revanced_hide_autoplay_button_summary_on">تم إخفاء زر التشغيل التلقائي</string> <string name="revanced_hide_autoplay_button_summary_on">تم إخفاء زر التشغيل التلقائي</string>
<string name="revanced_hide_autoplay_button_summary_off">يتم عرض زر التشغيل التلقائي</string> <string name="revanced_hide_autoplay_button_summary_off">يتم عرض زر التشغيل التلقائي</string>
<string name="revanced_hide_player_control_buttons_background_title">إخفاء خلفية أزرار التحكم في المشغل</string>
<string name="revanced_hide_player_control_buttons_background_summary_on">تم إخفاء خلفية أزرار التحكم في المشغل</string>
<string name="revanced_hide_player_control_buttons_background_summary_off">تم إظهار خلفية أزرار التحكم في المشغل</string>
</patch> </patch>
<patch id="layout.hide.endscreencards.hideEndscreenCardsResourcePatch"> <patch id="layout.hide.endscreencards.hideEndscreenCardsResourcePatch">
<string name="revanced_hide_endscreen_cards_title">إخفاء بطاقات شاشة النهاية</string> <string name="revanced_hide_endscreen_cards_title">إخفاء بطاقات شاشة النهاية</string>
@@ -735,6 +753,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_shorts_location_label_title">إخفاء تسمية الموقع</string> <string name="revanced_hide_shorts_location_label_title">إخفاء تسمية الموقع</string>
<string name="revanced_hide_shorts_location_label_summary_on">تم إخفاء تسمية الموقع</string> <string name="revanced_hide_shorts_location_label_summary_on">تم إخفاء تسمية الموقع</string>
<string name="revanced_hide_shorts_location_label_summary_off">يتم عرض تسمية الموقع</string> <string name="revanced_hide_shorts_location_label_summary_off">يتم عرض تسمية الموقع</string>
<string name="revanced_hide_shorts_preview_comment_title">إخفاء تعليق المعاينة</string>
<string name="revanced_hide_shorts_preview_comment_summary_on">تم إخفاء تعليق المعاينة</string>
<string name="revanced_hide_shorts_preview_comment_summary_off">يتم عرض تعليق المعاينة</string>
<string name="revanced_hide_shorts_save_sound_button_title">إخفاء زر حفظ الموسيقى</string> <string name="revanced_hide_shorts_save_sound_button_title">إخفاء زر حفظ الموسيقى</string>
<string name="revanced_hide_shorts_save_sound_button_summary_on">تم إخفاء زر حفظ الموسيقى</string> <string name="revanced_hide_shorts_save_sound_button_summary_on">تم إخفاء زر حفظ الموسيقى</string>
<string name="revanced_hide_shorts_save_sound_button_summary_off">يتم عرض زر حفظ الموسيقى</string> <string name="revanced_hide_shorts_save_sound_button_summary_off">يتم عرض زر حفظ الموسيقى</string>
@@ -747,6 +768,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_shorts_green_screen_button_title">إخفاء زر الشاشة الخضراء</string> <string name="revanced_hide_shorts_green_screen_button_title">إخفاء زر الشاشة الخضراء</string>
<string name="revanced_hide_shorts_green_screen_button_summary_on">تم إخفاء زر الشاشة الخضراء</string> <string name="revanced_hide_shorts_green_screen_button_summary_on">تم إخفاء زر الشاشة الخضراء</string>
<string name="revanced_hide_shorts_green_screen_button_summary_off">يتم عرض زر الشاشة الخضراء</string> <string name="revanced_hide_shorts_green_screen_button_summary_off">يتم عرض زر الشاشة الخضراء</string>
<string name="revanced_hide_shorts_new_posts_button_title">إخفاء زر \"مشاركات جديدة\"</string>
<string name="revanced_hide_shorts_new_posts_button_summary_off">يتم عرض زر \"مشاركات جديدة\"</string>
<string name="revanced_hide_shorts_new_posts_button_summary_on">تم إخفاء زر \"مشاركات جديدة\"</string>
<string name="revanced_hide_shorts_hashtag_button_title">إخفاء زر الهاشتاج</string> <string name="revanced_hide_shorts_hashtag_button_title">إخفاء زر الهاشتاج</string>
<string name="revanced_hide_shorts_hashtag_button_summary_on">تم إخفاء زر الهاشتاج</string> <string name="revanced_hide_shorts_hashtag_button_summary_on">تم إخفاء زر الهاشتاج</string>
<string name="revanced_hide_shorts_hashtag_button_summary_off">يتم عرض زر الهاشتاج</string> <string name="revanced_hide_shorts_hashtag_button_summary_off">يتم عرض زر الهاشتاج</string>
@@ -1238,6 +1262,9 @@ Second \"item\" text"</string>
<string name="revanced_gradient_loading_screen_title">تمكين شاشة التحميل المتدرجة</string> <string name="revanced_gradient_loading_screen_title">تمكين شاشة التحميل المتدرجة</string>
<string name="revanced_gradient_loading_screen_summary_on">ستحتوي شاشة التحميل على خلفية متدرجة</string> <string name="revanced_gradient_loading_screen_summary_on">ستحتوي شاشة التحميل على خلفية متدرجة</string>
<string name="revanced_gradient_loading_screen_summary_off">ستحتوي شاشة التحميل على خلفية ثابتة</string> <string name="revanced_gradient_loading_screen_summary_off">ستحتوي شاشة التحميل على خلفية ثابتة</string>
<string name="splash_screen_animation_style_title">نمط الشاشة الترحيبية</string>
<string name="splash_screen_animation_style_entry_1">اللون</string>
<string name="splash_screen_animation_style_entry_2">أبيض وأسود</string>
<string name="revanced_seekbar_custom_color_title">تمكين لون شريط تقدم الفيديو المخصص</string> <string name="revanced_seekbar_custom_color_title">تمكين لون شريط تقدم الفيديو المخصص</string>
<string name="revanced_seekbar_custom_color_summary_on">يتم عرض لون شريط تقدم الفيديو المخصص</string> <string name="revanced_seekbar_custom_color_summary_on">يتم عرض لون شريط تقدم الفيديو المخصص</string>
<string name="revanced_seekbar_custom_color_summary_off">يتم عرض لون شريط تقدم الفيديو الاصلي</string> <string name="revanced_seekbar_custom_color_summary_off">يتم عرض لون شريط تقدم الفيديو الاصلي</string>
@@ -1322,6 +1349,22 @@ Second \"item\" text"</string>
<string name="microg_settings_title">إعدادات GmsCore</string> <string name="microg_settings_title">إعدادات GmsCore</string>
<string name="microg_settings_summary">إعدادات لـ GmsCore</string> <string name="microg_settings_summary">إعدادات لـ GmsCore</string>
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
<string name="revanced_disable_haptic_feedback_title">الاهتزاز عند الضغط</string>
<string name="revanced_disable_haptic_feedback_summary">تغيير الاهتزاز عند الضغط</string>
<string name="revanced_disable_haptic_feedback_chapters_title">تعطيل الاهتزاز للفصول</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_on">تم تعطيل الاهتزاز للفصول</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_off">تم تفعيل الاهتزاز للفصول</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_title">تعطيل الاهتزاز عند التمرير الدقيق</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_on">تم تعطيل الاهتزاز الدقيق عند البحث</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_off">تم تفعيل الاهتزاز عند التمرير الدقيق</string>
<string name="revanced_disable_haptic_feedback_seek_undo_title">تعطيل الاهتزاز عند التراجع عن البحث</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_on">تم تعطيل الاهتزاز عند التراجع عن البحث</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_off">تم تمكين الاهتزاز عند التراجع عن البحث</string>
<string name="revanced_disable_haptic_feedback_zoom_title">تعطيل الاهتزاز عند التكبير</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_on">تم تعطيل الاهتزاز عند التكبير</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_off">تم تمكين الاهتزاز عند التكبير</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">إذا قمت مؤخرًا بتغيير تفاصيل تسجيل الدخول إلى حسابك، فأزل تثبيت MicroG ثم أعد تثبيته.</string> <string name="microg_offline_account_login_error">إذا قمت مؤخرًا بتغيير تفاصيل تسجيل الدخول إلى حسابك، فأزل تثبيت MicroG ثم أعد تثبيته.</string>
</patch> </patch>
@@ -1340,11 +1383,6 @@ Second \"item\" text"</string>
<string name="revanced_remove_tracking_query_parameter_summary_on">يتم إزالة معلمة استعلام التتبع من الروابط</string> <string name="revanced_remove_tracking_query_parameter_summary_on">يتم إزالة معلمة استعلام التتبع من الروابط</string>
<string name="revanced_remove_tracking_query_parameter_summary_off">لا يتم إزالة معلمة استعلام التتبع من الروابط</string> <string name="revanced_remove_tracking_query_parameter_summary_off">لا يتم إزالة معلمة استعلام التتبع من الروابط</string>
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
<string name="revanced_disable_zoom_haptics_title">تعطيل الاهتزاز عند التكبير</string>
<string name="revanced_disable_zoom_haptics_summary_on">تم تعطيل الاهتزاز</string>
<string name="revanced_disable_zoom_haptics_summary_off">تم تمكين الاهتزاز</string>
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<string name="revanced_force_original_audio_title">فرض لغة الصوت الأصلية</string> <string name="revanced_force_original_audio_title">فرض لغة الصوت الأصلية</string>
<string name="revanced_force_original_audio_summary_on">استخدام لغة الصوت الأصلية</string> <string name="revanced_force_original_audio_summary_on">استخدام لغة الصوت الأصلية</string>
@@ -1372,7 +1410,7 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="video.speed.button.playbackSpeedButtonPatch"> <patch id="video.speed.button.playbackSpeedButtonPatch">
<string name="revanced_playback_speed_dialog_button_title">عرض زر مربع حوار السرعة</string> <string name="revanced_playback_speed_dialog_button_title">عرض زر مربع حوار السرعة</string>
<string name="revanced_playback_speed_dialog_button_summary_on">يتم عرض الزر</string> <string name="revanced_playback_speed_dialog_button_summary_on">الزر معروض. انقر مع الاستمرار لإعادة ضبط سرعة التشغيل إلى الوضع الافتراضي</string>
<string name="revanced_playback_speed_dialog_button_summary_off">لا يتم عرض الزر</string> <string name="revanced_playback_speed_dialog_button_summary_off">لا يتم عرض الزر</string>
</patch> </patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch"> <patch id="video.speed.custom.customPlaybackSpeedPatch">
@@ -1384,6 +1422,7 @@ Second \"item\" text"</string>
<string name="revanced_custom_playback_speeds_invalid">يجب أن تكون سرعات التشغيل المخصصة أقل من %s</string> <string name="revanced_custom_playback_speeds_invalid">يجب أن تكون سرعات التشغيل المخصصة أقل من %s</string>
<string name="revanced_custom_playback_speeds_parse_exception">سرعة التشغيل المخصصة غير صالحة</string> <string name="revanced_custom_playback_speeds_parse_exception">سرعة التشغيل المخصصة غير صالحة</string>
<string name="revanced_custom_playback_speeds_auto">تلقائي</string> <string name="revanced_custom_playback_speeds_auto">تلقائي</string>
<string name="revanced_custom_playback_speeds_reset_toast">تمت إعادة ضبط سرعة التشغيل إلى: %s</string>
<string name="revanced_speed_tap_and_hold_title">سرعة النقر مع الاستمرار المخصصة</string> <string name="revanced_speed_tap_and_hold_title">سرعة النقر مع الاستمرار المخصصة</string>
<string name="revanced_speed_tap_and_hold_summary">سرعة التشغيل بين 0-8</string> <string name="revanced_speed_tap_and_hold_summary">سرعة التشغيل بين 0-8</string>
</patch> </patch>

View File

@@ -201,6 +201,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
</patch> </patch>
<patch id="misc.links.bypassURLRedirectsPatch"> <patch id="misc.links.bypassURLRedirectsPatch">
@@ -209,8 +211,6 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.privacy.removeTrackingQueryParameterPatch"> <patch id="misc.privacy.removeTrackingQueryParameterPatch">
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title --> <!-- 'Spoof video streams' should be the same translation used for revanced_spoof_video_streams_screen_title -->
</patch> </patch>

View File

@@ -35,6 +35,8 @@ Second \"item\" text"</string>
<string name="revanced_settings_submenu_title">Tənzimləmələr</string> <string name="revanced_settings_submenu_title">Tənzimləmələr</string>
<string name="revanced_settings_confirm_user_dialog_title">Davam etmək istəyirsiniz?</string> <string name="revanced_settings_confirm_user_dialog_title">Davam etmək istəyirsiniz?</string>
<string name="revanced_settings_reset">Sıfırla</string> <string name="revanced_settings_reset">Sıfırla</string>
<string name="revanced_settings_reset_color">Rəngi sıfırla</string>
<string name="revanced_settings_color_invalid">Yanlış rəng</string>
<string name="revanced_settings_restart_title">Yenilə və yenidən başlat</string> <string name="revanced_settings_restart_title">Yenilə və yenidən başlat</string>
<string name="revanced_settings_restart">Yenidən başlat</string> <string name="revanced_settings_restart">Yenidən başlat</string>
<string name="revanced_settings_import">İdxal et</string> <string name="revanced_settings_import">İdxal et</string>
@@ -115,6 +117,11 @@ Davam et düyməsinə toxun və optimallaşdırma dəyişikliklərin qəbul et."
<string name="revanced_debug_protobuffer_title">Bufer protokol jurnalı</string> <string name="revanced_debug_protobuffer_title">Bufer protokol jurnalı</string>
<string name="revanced_debug_protobuffer_summary_on">Sazlama jurnallarına protokol buferi daxildir</string> <string name="revanced_debug_protobuffer_summary_on">Sazlama jurnallarına protokol buferi daxildir</string>
<string name="revanced_debug_protobuffer_summary_off">Sazlama jurnallarına protokol buferi daxil deyil</string> <string name="revanced_debug_protobuffer_summary_off">Sazlama jurnallarına protokol buferi daxil deyil</string>
<string name="revanced_debug_protobuffer_user_dialog_message">"Bu seçimi aktivləşdirmə bəzi UI quruluşları üçün ekran mətni də olmaqla əlavə tərtibat bazasını daxil edəcək.
Bu, xüsusi filtrlər yaradarkən quruluşları müəyyən etməyə kömək edə bilər.
Hər halda, bunu aktivləşdirmə IP ünvanınız kimi bəzi istifadəçi məlumatın da daxil edəcək."</string>
<string name="revanced_debug_stacktrace_title">Yığın izləri jurnalı</string> <string name="revanced_debug_stacktrace_title">Yığın izləri jurnalı</string>
<string name="revanced_debug_stacktrace_summary_on">Sazlama jurnalına yığın izləri daxildir</string> <string name="revanced_debug_stacktrace_summary_on">Sazlama jurnalına yığın izləri daxildir</string>
<string name="revanced_debug_stacktrace_summary_off">Sazlama jurnalına yığın izləri daxil deyil</string> <string name="revanced_debug_stacktrace_summary_off">Sazlama jurnalına yığın izləri daxil deyil</string>
@@ -124,6 +131,15 @@ Davam et düyməsinə toxun və optimallaşdırma dəyişikliklərin qəbul et."
<string name="revanced_debug_toast_on_error_user_dialog_message">"Xəta ani bildirişlərin qapatmaq, bütün ReVanced xəta bildirişlərin gizlədir. <string name="revanced_debug_toast_on_error_user_dialog_message">"Xəta ani bildirişlərin qapatmaq, bütün ReVanced xəta bildirişlərin gizlədir.
Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string> Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_debug_export_logs_to_clipboard_title">Sazlama qeydlərini ixrac edin</string>
<string name="revanced_debug_export_logs_to_clipboard_summary">ReVanced sazlama qeydlərini buferə köçürür</string>
<string name="revanced_debug_logs_disabled">Sazlama qeydi qapalıdır</string>
<string name="revanced_debug_logs_none_found">Qeydlər tapılmadı</string>
<string name="revanced_debug_logs_copied_to_clipboard">Qeydlər köçürüldü</string>
<string name="revanced_debug_logs_failed_to_export">Qeydləri ixrac etmək alınmadı: $s</string>
<string name="revanced_debug_logs_clear_buffer_title">Sazlama qeydlərini təmizlə</string>
<string name="revanced_debug_logs_clear_buffer_summary">Saxlanılan bütün ReVanced sazlama qeydlərini təmizləyir</string>
<string name="revanced_debug_logs_clear_toast">Qeydlər silindi</string>
</patch> </patch>
<patch id="layout.hide.general.hideLayoutComponentsPatch"> <patch id="layout.hide.general.hideLayoutComponentsPatch">
<string name="revanced_hide_album_cards_title">Albom kartlarını gizlət</string> <string name="revanced_hide_album_cards_title">Albom kartlarını gizlət</string>
@@ -295,9 +311,9 @@ Gözlənilməz hallardan xəbərdar olmayacaqsınız."</string>
<string name="revanced_hide_comments_create_a_short_button_title">\"Shorts Yarat\" düyməsini gizlət</string> <string name="revanced_hide_comments_create_a_short_button_title">\"Shorts Yarat\" düyməsini gizlət</string>
<string name="revanced_hide_comments_create_a_short_button_summary_on">Short yarat düyməsi gizlidir</string> <string name="revanced_hide_comments_create_a_short_button_summary_on">Short yarat düyməsi gizlidir</string>
<string name="revanced_hide_comments_create_a_short_button_summary_off">Short yarat düyməsi görünür</string> <string name="revanced_hide_comments_create_a_short_button_summary_off">Short yarat düyməsi görünür</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_title">Emoji və vaxt möhürü düymələrin gizlət</string> <string name="revanced_hide_comments_timestamp_button_title">Vaxt möhürü düyməsini gizlət</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_summary_on">Emoji və vaxt möhürü düymələri gizlidir</string> <string name="revanced_hide_comments_timestamp_button_summary_on">Vaxt damğası düyməsi gizlidir</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_summary_off">Emoji və vaxt möhürü düymələri görünür</string> <string name="revanced_hide_comments_timestamp_button_summary_off">Vaxt möhürü düyməsi göstərilir</string>
<string name="revanced_hide_comments_preview_comment_title">Önbaxış şərhin gizlət</string> <string name="revanced_hide_comments_preview_comment_title">Önbaxış şərhin gizlət</string>
<string name="revanced_hide_comments_preview_comment_summary_on">Önbaxış şərhi gizlədilib</string> <string name="revanced_hide_comments_preview_comment_summary_on">Önbaxış şərhi gizlədilib</string>
<string name="revanced_hide_comments_preview_comment_summary_off">Önbaxış şərhi göstərilir</string> <string name="revanced_hide_comments_preview_comment_summary_off">Önbaxış şərhi göstərilir</string>
@@ -475,9 +491,10 @@ Ekranın sağ tərəfində düzünə sürüşdürərək səs səviyyəsini tənz
<string name="revanced_swipe_overlay_background_opacity_title">Sürüşdürmə cildi arxa plan qeyri-şəffaflığı</string> <string name="revanced_swipe_overlay_background_opacity_title">Sürüşdürmə cildi arxa plan qeyri-şəffaflığı</string>
<string name="revanced_swipe_overlay_background_opacity_summary">0-100 arası qeyri-şəffaflıq dəyəri</string> <string name="revanced_swipe_overlay_background_opacity_summary">0-100 arası qeyri-şəffaflıq dəyəri</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Sürüşmə qeyri-şəffaflığı 0-100 arası olmalıdır</string> <string name="revanced_swipe_overlay_background_opacity_invalid_toast">Sürüşmə qeyri-şəffaflığı 0-100 arası olmalıdır</string>
<string name="revanced_swipe_overlay_progress_color_title">Sürüşdürmə örtüyü irəliləyiş cizgisi rəngi</string> <string name="revanced_swipe_overlay_progress_brightness_color_title">Sürüşdürmə örtüyü parlaqlıq rəngi</string>
<string name="revanced_swipe_overlay_progress_color_summary">Səs səviyyəsinə və parlaqlığa nəzarət üçün irəliləyiş cizgisi rəngi</string> <string name="revanced_swipe_overlay_progress_brightness_color_summary">Parlaqlığa nəzarət üçün irəliləyiş zolağının rəngi</string>
<string name="revanced_swipe_overlay_progress_color_invalid_toast">Yanlış irəliləyiş cizgisi rəngi</string> <string name="revanced_swipe_overlay_progress_volume_color_title">Sürüşdürmə örtüyü səs səviyyəsi rəngi</string>
<string name="revanced_swipe_overlay_progress_volume_color_summary">Səs səviyyəsinə nəzarət üçün irəliləyiş zolağının rəngi</string>
<string name="revanced_swipe_text_overlay_size_title">Sürüşdürmə örtüyü mətn ölçüsü</string> <string name="revanced_swipe_text_overlay_size_title">Sürüşdürmə örtüyü mətn ölçüsü</string>
<string name="revanced_swipe_text_overlay_size_summary">Sürüşmə üçün mətn ölçüsü 1-30 arasındadır</string> <string name="revanced_swipe_text_overlay_size_summary">Sürüşmə üçün mətn ölçüsü 1-30 arasındadır</string>
<string name="revanced_swipe_text_overlay_size_invalid_toast">Mətn ölçüsü 1-30 arası olmalıdır</string> <string name="revanced_swipe_text_overlay_size_invalid_toast">Mətn ölçüsü 1-30 arası olmalıdır</string>
@@ -733,6 +750,9 @@ Audio trek seçimin göstərmək üçün \"Video axınları saxtalaşdır\"ı iO
<string name="revanced_hide_shorts_location_label_title">Məkan etiketini gizlət</string> <string name="revanced_hide_shorts_location_label_title">Məkan etiketini gizlət</string>
<string name="revanced_hide_shorts_location_label_summary_on">Məkan etiketi gizlidir</string> <string name="revanced_hide_shorts_location_label_summary_on">Məkan etiketi gizlidir</string>
<string name="revanced_hide_shorts_location_label_summary_off">Məkan etiketi göstərilir</string> <string name="revanced_hide_shorts_location_label_summary_off">Məkan etiketi göstərilir</string>
<string name="revanced_hide_shorts_preview_comment_title">Öncül baxış şərhini gizlət</string>
<string name="revanced_hide_shorts_preview_comment_summary_on">Öncül baxış şərhi gizlidir</string>
<string name="revanced_hide_shorts_preview_comment_summary_off">Öncül baxış şərhi göstərilir</string>
<string name="revanced_hide_shorts_save_sound_button_title">Musiqini saxla düyməsini gizlət</string> <string name="revanced_hide_shorts_save_sound_button_title">Musiqini saxla düyməsini gizlət</string>
<string name="revanced_hide_shorts_save_sound_button_summary_on">\"Musiqini saxla\" düyməsi gizlidir</string> <string name="revanced_hide_shorts_save_sound_button_summary_on">\"Musiqini saxla\" düyməsi gizlidir</string>
<string name="revanced_hide_shorts_save_sound_button_summary_off">\"Musiqini saxla\" düyməsi göstərilir</string> <string name="revanced_hide_shorts_save_sound_button_summary_off">\"Musiqini saxla\" düyməsi göstərilir</string>
@@ -1235,6 +1255,9 @@ Genişləndirmək və ya bağlamaq üçün sürüşdür"</string>
<string name="revanced_gradient_loading_screen_title">Dəyişkən yükləmə ekranını aktivləşdir</string> <string name="revanced_gradient_loading_screen_title">Dəyişkən yükləmə ekranını aktivləşdir</string>
<string name="revanced_gradient_loading_screen_summary_on">Yükləmə ekranı, dəyişkən arxa plana malik olacaq</string> <string name="revanced_gradient_loading_screen_summary_on">Yükləmə ekranı, dəyişkən arxa plana malik olacaq</string>
<string name="revanced_gradient_loading_screen_summary_off">Yükləmə ekranı, vahid arxa plana malik olacaq</string> <string name="revanced_gradient_loading_screen_summary_off">Yükləmə ekranı, vahid arxa plana malik olacaq</string>
<string name="splash_screen_animation_style_title">Sıçrama ekran üslubu</string>
<string name="splash_screen_animation_style_entry_1">Rəng</string>
<string name="splash_screen_animation_style_entry_2">Qara və ağ</string>
<string name="revanced_seekbar_custom_color_title">Fərdi irəliləmə cizgisi rəngini aktivləşdir</string> <string name="revanced_seekbar_custom_color_title">Fərdi irəliləmə cizgisi rəngini aktivləşdir</string>
<string name="revanced_seekbar_custom_color_summary_on">Fərdi irəliləmə cizgisi rəngi göstərilir</string> <string name="revanced_seekbar_custom_color_summary_on">Fərdi irəliləmə cizgisi rəngi göstərilir</string>
<string name="revanced_seekbar_custom_color_summary_off">Orijinal irəliləmə cizgisi rəngi göstərilir</string> <string name="revanced_seekbar_custom_color_summary_off">Orijinal irəliləmə cizgisi rəngi göstərilir</string>
@@ -1319,6 +1342,22 @@ Bunu aktivləşdirmə daha yüksək video keyfiyyətləri əngəlin silə bilər
<string name="microg_settings_title">GmsCore Tənzimləmələri</string> <string name="microg_settings_title">GmsCore Tənzimləmələri</string>
<string name="microg_settings_summary">GmsCore üçün Tənzimləmələr</string> <string name="microg_settings_summary">GmsCore üçün Tənzimləmələr</string>
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
<string name="revanced_disable_haptic_feedback_title">Əks-əlaqə reaksiyası</string>
<string name="revanced_disable_haptic_feedback_summary">Əks-əlaqə reaksiyasını dəyişdir</string>
<string name="revanced_disable_haptic_feedback_chapters_title">Fəsillər reaksiyasın qapat</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_on">Fəsillər reaksiyası qapalıdır</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_off">Fəsillər reaksiyası aktivdir</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_title">Dəqiq axtarış reaksiyasın qapat</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_on">Dəqiq axtarış reaksiyası qapalıdır</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_off">Dəqiq axtarış reaksiyası aktivdir</string>
<string name="revanced_disable_haptic_feedback_seek_undo_title">Axtarış geriyə reaksiyasın qapat</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_on">Axtarış geri reaksiyası qapalıdır</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_off">Axtarış geri reaksiyası aktivdir</string>
<string name="revanced_disable_haptic_feedback_zoom_title">Yaxınlaşdırma reaksiyasın qapat</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_on">Yaxınlaşdırma reaksiyası qapalıdır</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_off">Yaxınlaşdırma reaksiyası aktivdir</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">Bu yaxınlarda hesabınıza giriş məlumatlarınızı dəyişmisinizsə, MicroG-ni silin və təkrar quraşdırın.</string> <string name="microg_offline_account_login_error">Bu yaxınlarda hesabınıza giriş məlumatlarınızı dəyişmisinizsə, MicroG-ni silin və təkrar quraşdırın.</string>
</patch> </patch>
@@ -1337,11 +1376,6 @@ Bunu aktivləşdirmə daha yüksək video keyfiyyətləri əngəlin silə bilər
<string name="revanced_remove_tracking_query_parameter_summary_on">İzləmə sorğusu faktoru bağlantılardan silinir</string> <string name="revanced_remove_tracking_query_parameter_summary_on">İzləmə sorğusu faktoru bağlantılardan silinir</string>
<string name="revanced_remove_tracking_query_parameter_summary_off">İzləmə sorğusu faktoru bağlantılardan silinmir</string> <string name="revanced_remove_tracking_query_parameter_summary_off">İzləmə sorğusu faktoru bağlantılardan silinmir</string>
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
<string name="revanced_disable_zoom_haptics_title">Yaxınlaşdırma reaksiyasın qapat</string>
<string name="revanced_disable_zoom_haptics_summary_on">Reaksiya qapalıdır</string>
<string name="revanced_disable_zoom_haptics_summary_off">Reaksiya aktivdir</string>
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<string name="revanced_force_original_audio_title">Orijinal səs dilini zorla</string> <string name="revanced_force_original_audio_title">Orijinal səs dilini zorla</string>
<string name="revanced_force_original_audio_summary_on">Orijinal səs dilini istifadə</string> <string name="revanced_force_original_audio_summary_on">Orijinal səs dilini istifadə</string>
@@ -1369,7 +1403,7 @@ Bunu aktivləşdirmə daha yüksək video keyfiyyətləri əngəlin silə bilər
</patch> </patch>
<patch id="video.speed.button.playbackSpeedButtonPatch"> <patch id="video.speed.button.playbackSpeedButtonPatch">
<string name="revanced_playback_speed_dialog_button_title">Sürət dialoq düyməsini göstər</string> <string name="revanced_playback_speed_dialog_button_title">Sürət dialoq düyməsini göstər</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Düymə göstərilir</string> <string name="revanced_playback_speed_dialog_button_summary_on">Düymə göstərilir. Oynatma sürətin standart olaraq qaytarmaq üçün toxunub saxla</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Düymə göstərilmir</string> <string name="revanced_playback_speed_dialog_button_summary_off">Düymə göstərilmir</string>
</patch> </patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch"> <patch id="video.speed.custom.customPlaybackSpeedPatch">
@@ -1381,6 +1415,7 @@ Bunu aktivləşdirmə daha yüksək video keyfiyyətləri əngəlin silə bilər
<string name="revanced_custom_playback_speeds_invalid">Fərdi sürətlər %s dəyərindən az olmalıdır</string> <string name="revanced_custom_playback_speeds_invalid">Fərdi sürətlər %s dəyərindən az olmalıdır</string>
<string name="revanced_custom_playback_speeds_parse_exception">Fərdi oynatma sürətləri etibarsızdır</string> <string name="revanced_custom_playback_speeds_parse_exception">Fərdi oynatma sürətləri etibarsızdır</string>
<string name="revanced_custom_playback_speeds_auto">Avtomatik</string> <string name="revanced_custom_playback_speeds_auto">Avtomatik</string>
<string name="revanced_custom_playback_speeds_reset_toast">Oynatma sürəti sıfırlandı: %s</string>
<string name="revanced_speed_tap_and_hold_title">Xüsusi toxunma və saxlanılma sürəti</string> <string name="revanced_speed_tap_and_hold_title">Xüsusi toxunma və saxlanılma sürəti</string>
<string name="revanced_speed_tap_and_hold_summary">0-8 arası oynatma sürəti</string> <string name="revanced_speed_tap_and_hold_summary">0-8 arası oynatma sürəti</string>
</patch> </patch>

View File

@@ -117,6 +117,11 @@ Second \"item\" text"</string>
<string name="revanced_debug_protobuffer_title">Буфер пратаколу часопіса</string> <string name="revanced_debug_protobuffer_title">Буфер пратаколу часопіса</string>
<string name="revanced_debug_protobuffer_summary_on">Журналы адладкі ўключаюць пратабуфер</string> <string name="revanced_debug_protobuffer_summary_on">Журналы адладкі ўключаюць пратабуфер</string>
<string name="revanced_debug_protobuffer_summary_off">Журналы адладкі не ўключаюць пратабуфер</string> <string name="revanced_debug_protobuffer_summary_off">Журналы адладкі не ўключаюць пратабуфер</string>
<string name="revanced_debug_protobuffer_user_dialog_message">"Уключэнне гэтага параметра будзе запісваць дадатковыя даныя макета, у тым ліку тэкст на экране для некаторых кампанентаў інтэрфейсу.
Гэта можа дапамагчы ідэнтыфікаваць кампаненты пры стварэнні карыстацкіх фільтраў.
Аднак уключэнне гэтага параметра таксама будзе запісваць некаторыя даныя карыстальніка, такія як ваш IP-адрас."</string>
<string name="revanced_debug_stacktrace_title">Сляды стэка журнала</string> <string name="revanced_debug_stacktrace_title">Сляды стэка журнала</string>
<string name="revanced_debug_stacktrace_summary_on">Журналы адладкі ўключаюць трасіроўку стэка</string> <string name="revanced_debug_stacktrace_summary_on">Журналы адладкі ўключаюць трасіроўку стэка</string>
<string name="revanced_debug_stacktrace_summary_off">Журналы адладкі не ўключаюць трасіроўку стэка</string> <string name="revanced_debug_stacktrace_summary_off">Журналы адладкі не ўключаюць трасіроўку стэка</string>
@@ -126,6 +131,15 @@ Second \"item\" text"</string>
<string name="revanced_debug_toast_on_error_user_dialog_message">"Адключэнне паведамленняў пра памылкі схавае ўсе апавяшчэнні ReVanced пра памылкі. <string name="revanced_debug_toast_on_error_user_dialog_message">"Адключэнне паведамленняў пра памылкі схавае ўсе апавяшчэнні ReVanced пра памылкі.
Вы не будзеце атрымліваць апавяшчэнні пра нечаканыя падзеі."</string> Вы не будзеце атрымліваць апавяшчэнні пра нечаканыя падзеі."</string>
<string name="revanced_debug_export_logs_to_clipboard_title">Экспартаваць адладачныя лагі</string>
<string name="revanced_debug_export_logs_to_clipboard_summary">Капіруе адладачныя лагі ReVanced у буфер абмену</string>
<string name="revanced_debug_logs_disabled">Адладачнае лагаванне адключана</string>
<string name="revanced_debug_logs_none_found">Лагі не знойдзены</string>
<string name="revanced_debug_logs_copied_to_clipboard">Лагі скапіяваны</string>
<string name="revanced_debug_logs_failed_to_export">Не атрымалася экспартаваць лагі: $s</string>
<string name="revanced_debug_logs_clear_buffer_title">Ачысціць адладачныя лагі</string>
<string name="revanced_debug_logs_clear_buffer_summary">Ачышчае ўсе захаваныя адладачныя лагі ReVanced</string>
<string name="revanced_debug_logs_clear_toast">Лагі ачышчаны</string>
</patch> </patch>
<patch id="layout.hide.general.hideLayoutComponentsPatch"> <patch id="layout.hide.general.hideLayoutComponentsPatch">
<string name="revanced_hide_album_cards_title">Схаваць карты альбома</string> <string name="revanced_hide_album_cards_title">Схаваць карты альбома</string>
@@ -297,9 +311,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_comments_create_a_short_button_title">Схаваць кнопку \"Створиць Short\"</string> <string name="revanced_hide_comments_create_a_short_button_title">Схаваць кнопку \"Створиць Short\"</string>
<string name="revanced_hide_comments_create_a_short_button_summary_on">Кнопка «Стварыць Shorts» схаваная</string> <string name="revanced_hide_comments_create_a_short_button_summary_on">Кнопка «Стварыць Shorts» схаваная</string>
<string name="revanced_hide_comments_create_a_short_button_summary_off">Кнопка «Стварыць Shorts» паказаная</string> <string name="revanced_hide_comments_create_a_short_button_summary_off">Кнопка «Стварыць Shorts» паказаная</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_title">Схаваць кнопкі эмодзі і адзнак часу</string> <string name="revanced_hide_comments_timestamp_button_title">Схаваць кнопку часу</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_summary_on">Кнопкі эмодзі і адзнак часу схаваныя</string> <string name="revanced_hide_comments_timestamp_button_summary_on">Кнопка часу схаваная</string>
<string name="revanced_hide_comments_timestamp_and_emoji_buttons_summary_off">Кнопкі эмодзі і адзнак часу паказаныя</string> <string name="revanced_hide_comments_timestamp_button_summary_off">Кнопка часу паказаная</string>
<string name="revanced_hide_comments_preview_comment_title">Схаваць каментарый для папярэдняга прагляду</string> <string name="revanced_hide_comments_preview_comment_title">Схаваць каментарый для папярэдняга прагляду</string>
<string name="revanced_hide_comments_preview_comment_summary_on">Каментарый перад праглядам схаваны</string> <string name="revanced_hide_comments_preview_comment_summary_on">Каментарый перад праглядам схаваны</string>
<string name="revanced_hide_comments_preview_comment_summary_off">Паказваецца папярэдні прагляд каментарыя</string> <string name="revanced_hide_comments_preview_comment_summary_off">Паказваецца папярэдні прагляд каментарыя</string>
@@ -477,9 +491,10 @@ Second \"item\" text"</string>
<string name="revanced_swipe_overlay_background_opacity_title">Непразрыстасць фону накладкі пракруткі</string> <string name="revanced_swipe_overlay_background_opacity_title">Непразрыстасць фону накладкі пракруткі</string>
<string name="revanced_swipe_overlay_background_opacity_summary">Значэнне непразрыстасці паміж 0-100</string> <string name="revanced_swipe_overlay_background_opacity_summary">Значэнне непразрыстасці паміж 0-100</string>
<string name="revanced_swipe_overlay_background_opacity_invalid_toast">Непразрыстасць пракруткі павінна быць паміж 0-100</string> <string name="revanced_swipe_overlay_background_opacity_invalid_toast">Непразрыстасць пракруткі павінна быць паміж 0-100</string>
<string name="revanced_swipe_overlay_progress_color_title">Колер слупка прагрэсу накладкі правядзення</string> <string name="revanced_swipe_overlay_progress_brightness_color_title">Колер яркасці накладкі правядзення</string>
<string name="revanced_swipe_overlay_progress_color_summary">Колер слупка прагрэсу для рэгулявання гучнасці і яркасці</string> <string name="revanced_swipe_overlay_progress_brightness_color_summary">Колер шкалы прагрэсу для элементаў кіравання яркасцю</string>
<string name="revanced_swipe_overlay_progress_color_invalid_toast">Несапраўдны колер слупка прагрэсу</string> <string name="revanced_swipe_overlay_progress_volume_color_title">Колер гучнасці накладкі правядзення</string>
<string name="revanced_swipe_overlay_progress_volume_color_summary">Колер шкалы прагрэсу для элементаў кіравання гучнасцю</string>
<string name="revanced_swipe_text_overlay_size_title">Памер тэксту накладкі правядзення</string> <string name="revanced_swipe_text_overlay_size_title">Памер тэксту накладкі правядзення</string>
<string name="revanced_swipe_text_overlay_size_summary">Памер тэксту для накладкі правядзення ад 1 да 30</string> <string name="revanced_swipe_text_overlay_size_summary">Памер тэксту для накладкі правядзення ад 1 да 30</string>
<string name="revanced_swipe_text_overlay_size_invalid_toast">Памер тэксту павінен быць у межах ад 1 да 30</string> <string name="revanced_swipe_text_overlay_size_invalid_toast">Памер тэксту павінен быць у межах ад 1 да 30</string>
@@ -666,6 +681,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_autoplay_button_title">Схаваць кнопку «Аўтапрайграванне»</string> <string name="revanced_hide_autoplay_button_title">Схаваць кнопку «Аўтапрайграванне»</string>
<string name="revanced_hide_autoplay_button_summary_on">Кнопка аўтазапуску схавана</string> <string name="revanced_hide_autoplay_button_summary_on">Кнопка аўтазапуску схавана</string>
<string name="revanced_hide_autoplay_button_summary_off">Паказана кнопка аўтазапуску</string> <string name="revanced_hide_autoplay_button_summary_off">Паказана кнопка аўтазапуску</string>
<string name="revanced_hide_player_control_buttons_background_title">Схаваць фон кнопак кіравання прайгравальнікам</string>
<string name="revanced_hide_player_control_buttons_background_summary_on">Фон кнопак кіравання прайгравальнікам схаваны</string>
<string name="revanced_hide_player_control_buttons_background_summary_off">Фон кнопак кіравання прайгравальнікам паказаны</string>
</patch> </patch>
<patch id="layout.hide.endscreencards.hideEndscreenCardsResourcePatch"> <patch id="layout.hide.endscreencards.hideEndscreenCardsResourcePatch">
<string name="revanced_hide_endscreen_cards_title">Схаваць карткі канцавога экрана</string> <string name="revanced_hide_endscreen_cards_title">Схаваць карткі канцавога экрана</string>
@@ -735,6 +753,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_shorts_location_label_title">Схаваць метку месцазнаходжання</string> <string name="revanced_hide_shorts_location_label_title">Схаваць метку месцазнаходжання</string>
<string name="revanced_hide_shorts_location_label_summary_on">Метка месцазнаходжання схавана</string> <string name="revanced_hide_shorts_location_label_summary_on">Метка месцазнаходжання схавана</string>
<string name="revanced_hide_shorts_location_label_summary_off">Паказана метка месцазнаходжання</string> <string name="revanced_hide_shorts_location_label_summary_off">Паказана метка месцазнаходжання</string>
<string name="revanced_hide_shorts_preview_comment_title">Схаваць папярэдні прагляд каментарыя</string>
<string name="revanced_hide_shorts_preview_comment_summary_on">Папярэдні прагляд каментарыя схаваны</string>
<string name="revanced_hide_shorts_preview_comment_summary_off">Паказваецца папярэдні прагляд каментарыя</string>
<string name="revanced_hide_shorts_save_sound_button_title">Схаваць кнопку «Захаваць музыку»</string> <string name="revanced_hide_shorts_save_sound_button_title">Схаваць кнопку «Захаваць музыку»</string>
<string name="revanced_hide_shorts_save_sound_button_summary_on">Кнопка захавання музыкі схавана</string> <string name="revanced_hide_shorts_save_sound_button_summary_on">Кнопка захавання музыкі схавана</string>
<string name="revanced_hide_shorts_save_sound_button_summary_off">Кнопка захавання музыкі паказана</string> <string name="revanced_hide_shorts_save_sound_button_summary_off">Кнопка захавання музыкі паказана</string>
@@ -747,6 +768,9 @@ Second \"item\" text"</string>
<string name="revanced_hide_shorts_green_screen_button_title">Схаваць кнопку «Зялёны экран»</string> <string name="revanced_hide_shorts_green_screen_button_title">Схаваць кнопку «Зялёны экран»</string>
<string name="revanced_hide_shorts_green_screen_button_summary_on">Кнопка с зелёным экраном Shorts скрыта</string> <string name="revanced_hide_shorts_green_screen_button_summary_on">Кнопка с зелёным экраном Shorts скрыта</string>
<string name="revanced_hide_shorts_green_screen_button_summary_off">Кнопка с зелёным экраном Shorts отображается</string> <string name="revanced_hide_shorts_green_screen_button_summary_off">Кнопка с зелёным экраном Shorts отображается</string>
<string name="revanced_hide_shorts_new_posts_button_title">Схаваць кнопку «Новыя паведамленні»</string>
<string name="revanced_hide_shorts_new_posts_button_summary_off">Кнопка «Новыя паведамленні» паказана</string>
<string name="revanced_hide_shorts_new_posts_button_summary_on">Кнопка «Новыя паведамленні» схавана</string>
<string name="revanced_hide_shorts_hashtag_button_title">Скрыть хештег-кнопку Shorts</string> <string name="revanced_hide_shorts_hashtag_button_title">Скрыть хештег-кнопку Shorts</string>
<string name="revanced_hide_shorts_hashtag_button_summary_on">Хештег-кнопка Shorts скрыта</string> <string name="revanced_hide_shorts_hashtag_button_summary_on">Хештег-кнопка Shorts скрыта</string>
<string name="revanced_hide_shorts_hashtag_button_summary_off">Хештег-кнопка Shorts отображается</string> <string name="revanced_hide_shorts_hashtag_button_summary_off">Хештег-кнопка Shorts отображается</string>
@@ -1239,6 +1263,9 @@ Second \"item\" text"</string>
<string name="revanced_gradient_loading_screen_title">Уключыць градыентны экран загрузкі</string> <string name="revanced_gradient_loading_screen_title">Уключыць градыентны экран загрузкі</string>
<string name="revanced_gradient_loading_screen_summary_on">Экран загрузкі будзе мець градыентны фон</string> <string name="revanced_gradient_loading_screen_summary_on">Экран загрузкі будзе мець градыентны фон</string>
<string name="revanced_gradient_loading_screen_summary_off">Экран загрузкі будзе мець суцэльны фон</string> <string name="revanced_gradient_loading_screen_summary_off">Экран загрузкі будзе мець суцэльны фон</string>
<string name="splash_screen_animation_style_title">Стыль застаўкі</string>
<string name="splash_screen_animation_style_entry_1">Колер</string>
<string name="splash_screen_animation_style_entry_2">Чорна-белы</string>
<string name="revanced_seekbar_custom_color_title">Уключыць уласны колер панэлі пошуку</string> <string name="revanced_seekbar_custom_color_title">Уключыць уласны колер панэлі пошуку</string>
<string name="revanced_seekbar_custom_color_summary_on">Паказваецца карыстальніцкі колер панэлі пошуку</string> <string name="revanced_seekbar_custom_color_summary_on">Паказваецца карыстальніцкі колер панэлі пошуку</string>
<string name="revanced_seekbar_custom_color_summary_off">Паказаны зыходны колер панэлі пошуку</string> <string name="revanced_seekbar_custom_color_summary_off">Паказаны зыходны колер панэлі пошуку</string>
@@ -1323,6 +1350,22 @@ Second \"item\" text"</string>
<string name="microg_settings_title">Налады GmsCore</string> <string name="microg_settings_title">Налады GmsCore</string>
<string name="microg_settings_summary">Налады для GmsCore</string> <string name="microg_settings_summary">Налады для GmsCore</string>
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
<string name="revanced_disable_haptic_feedback_title">Тактыльная зваротная сувязь</string>
<string name="revanced_disable_haptic_feedback_summary">Змяніць тактыльную зваротную сувязь</string>
<string name="revanced_disable_haptic_feedback_chapters_title">Адключыць тактыльныя эфекты раздзелаў</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_on">Тактыльныя эфекты раздзелаў адключаны</string>
<string name="revanced_disable_haptic_feedback_chapters_summary_off">Тактыльныя эфекты раздзелаў уключаны</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_title">Адключыць тактыльны эфект дакладнага пошуку</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_on">Дакладны тактыльны эфект пошуку адключаны</string>
<string name="revanced_disable_haptic_feedback_precise_seeking_summary_off">Тактыльны эфект дакладнага пошуку ўключаны</string>
<string name="revanced_disable_haptic_feedback_seek_undo_title">Адключыць тактыльны эфект адмены пошуку</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_on">Тактыльны эфект адмены пошуку адключаны</string>
<string name="revanced_disable_haptic_feedback_seek_undo_summary_off">Тактыльны эфект адмены пошуку ўключаны</string>
<string name="revanced_disable_haptic_feedback_zoom_title">Адключыць тактыльны эфект маштабавання</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_on">Тактыльны эфект маштабавання адключаны</string>
<string name="revanced_disable_haptic_feedback_zoom_summary_off">Тактыльны эфект маштабавання ўключаны</string>
</patch>
<patch id="misc.gms.accountCredentialsInvalidTextPatch"> <patch id="misc.gms.accountCredentialsInvalidTextPatch">
<string name="microg_offline_account_login_error">Калі вы нядаўна змянілі даныя для ўваходу ў свой уліковы запіс, выдаліце і пераўсталюйце MicroG.</string> <string name="microg_offline_account_login_error">Калі вы нядаўна змянілі даныя для ўваходу ў свой уліковы запіс, выдаліце і пераўсталюйце MicroG.</string>
</patch> </patch>
@@ -1341,11 +1384,6 @@ Second \"item\" text"</string>
<string name="revanced_remove_tracking_query_parameter_summary_on">Параметр запыту адсочвання выдалены са спасылак</string> <string name="revanced_remove_tracking_query_parameter_summary_on">Параметр запыту адсочвання выдалены са спасылак</string>
<string name="revanced_remove_tracking_query_parameter_summary_off">Параметр адсочвання запыту не выдаляецца са спасылак</string> <string name="revanced_remove_tracking_query_parameter_summary_off">Параметр адсочвання запыту не выдаляецца са спасылак</string>
</patch> </patch>
<patch id="misc.zoomhaptics.zoomHapticsPatch">
<string name="revanced_disable_zoom_haptics_title">Адключыць тактыльны эфект маштабавання</string>
<string name="revanced_disable_zoom_haptics_summary_on">Тактыльныя функцыі адключаны</string>
<string name="revanced_disable_zoom_haptics_summary_off">Тактыльныя сігналы ўключаны</string>
</patch>
<patch id="video.audio.forceOriginalAudioPatch"> <patch id="video.audio.forceOriginalAudioPatch">
<string name="revanced_force_original_audio_title">Вымушаная арыгінальная мова аўдыё</string> <string name="revanced_force_original_audio_title">Вымушаная арыгінальная мова аўдыё</string>
<string name="revanced_force_original_audio_summary_on">Выкарыстоўваць арыгінальную мову аўдыя</string> <string name="revanced_force_original_audio_summary_on">Выкарыстоўваць арыгінальную мову аўдыя</string>
@@ -1373,7 +1411,7 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="video.speed.button.playbackSpeedButtonPatch"> <patch id="video.speed.button.playbackSpeedButtonPatch">
<string name="revanced_playback_speed_dialog_button_title">Паказаць дыялогавую кнопку хуткасці</string> <string name="revanced_playback_speed_dialog_button_title">Паказаць дыялогавую кнопку хуткасці</string>
<string name="revanced_playback_speed_dialog_button_summary_on">Паказана кнопка</string> <string name="revanced_playback_speed_dialog_button_summary_on">Кнопка паказана. Націсніце і ўтрымлівайце, каб скінуць хуткасць прайгравання да стандартнай</string>
<string name="revanced_playback_speed_dialog_button_summary_off">Кнопка не паказваецца</string> <string name="revanced_playback_speed_dialog_button_summary_off">Кнопка не паказваецца</string>
</patch> </patch>
<patch id="video.speed.custom.customPlaybackSpeedPatch"> <patch id="video.speed.custom.customPlaybackSpeedPatch">
@@ -1385,6 +1423,7 @@ Second \"item\" text"</string>
<string name="revanced_custom_playback_speeds_invalid">Нестандартныя хуткасці павінны быць менш за %s</string> <string name="revanced_custom_playback_speeds_invalid">Нестандартныя хуткасці павінны быць менш за %s</string>
<string name="revanced_custom_playback_speeds_parse_exception">Несапраўдныя нестандартныя хуткасці прайгравання</string> <string name="revanced_custom_playback_speeds_parse_exception">Несапраўдныя нестандартныя хуткасці прайгравання</string>
<string name="revanced_custom_playback_speeds_auto">Аўто</string> <string name="revanced_custom_playback_speeds_auto">Аўто</string>
<string name="revanced_custom_playback_speeds_reset_toast">Хуткасць прайгравання скінута да: %s</string>
<string name="revanced_speed_tap_and_hold_title">Уласны хуткасць націску і ўтрымання</string> <string name="revanced_speed_tap_and_hold_title">Уласны хуткасць націску і ўтрымання</string>
<string name="revanced_speed_tap_and_hold_summary">Хуткасць прайгравання між 0-8</string> <string name="revanced_speed_tap_and_hold_summary">Хуткасць прайгравання між 0-8</string>
</patch> </patch>

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