Compare commits

...

145 Commits

Author SHA1 Message Date
semantic-release-bot
2c51de59de chore: Release v5.14.0-dev.5 [skip ci]
# [5.14.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.4...v5.14.0-dev.5) (2025-03-06)

### Features

* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([df3dc1c](df3dc1c0b2))
2025-03-06 18:27:44 +00:00
ILoveOpenSourceApplications
df3dc1c0b2 feat(Infinity for Reddit): Add support for package name on IzzyOnDroid (#4554) 2025-03-06 20:24:54 +02:00
github-actions[bot]
074c948581 chore: Sync translations (#4556) 2025-03-06 20:24:22 +02:00
semantic-release-bot
2a88b1f895 chore: Release v5.14.0-dev.4 [skip ci]
# [5.14.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.3...v5.14.0-dev.4) (2025-03-06)

### Bug Fixes

* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee5c830](ee5c830df8))
2025-03-06 12:59:39 +00:00
LisoUseInAIKyrios
ee5c830df8 fix(YouTube): Combine Restore old video quality menu and Remember video quality into Video quality patch (#4552) 2025-03-06 14:56:32 +02:00
semantic-release-bot
e63a4b31f3 chore: Release v5.14.0-dev.3 [skip ci]
# [5.14.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.2...v5.14.0-dev.3) (2025-03-06)

### Bug Fixes

* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([8d0bca3](8d0bca3b03))
2025-03-06 10:36:52 +00:00
oSumAtrIX
8d0bca3b03 fix(Boost for reddit - Client spoof): Use a different user agent to combat Reddit's API issues 2025-03-06 11:33:06 +01:00
semantic-release-bot
c162d65d5b chore: Release v5.14.0-dev.2 [skip ci]
# [5.14.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.1...v5.14.0-dev.2) (2025-03-06)

### Bug Fixes

* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([67dcd09](67dcd091c4))
2025-03-06 09:30:51 +00:00
ILoveOpenSourceApplications
67dcd091c4 fix(YouTube - Hide ads): Hide new type of buttoned ad (#4528) 2025-03-06 11:27:37 +02:00
github-actions[bot]
ac5ce2d67f chore: Sync translations (#4553) 2025-03-06 11:26:20 +02:00
LisoUseInAIKyrios
4b78d056fd ci: Pull Crowdin strings less often
Crowdin is starting to give errors and pulling less often may help.
2025-03-06 11:25:22 +02:00
semantic-release-bot
f8c901b2c1 chore: Release v5.14.0-dev.1 [skip ci]
# [5.14.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.1-dev.1...v5.14.0-dev.1) (2025-03-06)

### Features

* **YouTube - Remember video quality:** Add separate Shorts default quality settings ([#4543](https://github.com/ReVanced/revanced-patches/issues/4543)) ([2a67c31](2a67c312e1))
2025-03-06 06:49:39 +00:00
alieRN
2a67c312e1 feat(YouTube - Remember video quality): Add separate Shorts default quality settings (#4543) 2025-03-06 08:46:33 +02:00
semantic-release-bot
a7eed30f46 chore: Release v5.13.1-dev.1 [skip ci]
## [5.13.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.0...v5.13.1-dev.1) (2025-03-06)

### Bug Fixes

* **YouTube - Change form factor:** Restore Automotive form factor watch history menu, channel pages, and community posts ([#4541](https://github.com/ReVanced/revanced-patches/issues/4541)) ([e2de2d8](e2de2d8d44))
2025-03-06 06:28:57 +00:00
LisoUseInAIKyrios
e2de2d8d44 fix(YouTube - Change form factor): Restore Automotive form factor watch history menu, channel pages, and community posts (#4541) 2025-03-06 08:26:09 +02:00
github-actions[bot]
7ebbf356c0 chore: Sync translations (#4550) 2025-03-06 08:25:49 +02:00
ILoveOpenSourceApplications
2ced5c6e2a refactor(YouTube): Use more consistent strings (#4526) 2025-03-05 08:55:44 +02:00
semantic-release-bot
4a090ba659 chore: Release v5.13.0 [skip ci]
# [5.13.0](https://github.com/ReVanced/revanced-patches/compare/v5.12.0...v5.13.0) (2025-03-03)

### Bug Fixes

* **TikTok:** Resolve startup app crash ([3c52ab8](3c52ab8017))
* **TikTok:** Resolve startup app crash ([c817977](c8179776ed))
* **TikTok:** Resolve startup app crash ([d5aab3d](d5aab3d464))
* **TikTok:** Resolve startup app crash ([348f7e1](348f7e12cb))
* **YouTube - Copy video URL:** Use correct button ordering ([d77d5bf](d77d5bfbdd))
* **YouTube - Hide filter bar:** Fix `Hide in feed` not working in subscriptions feed ([#4512](https://github.com/ReVanced/revanced-patches/issues/4512)) ([1b60a72](1b60a72ede))
* **YouTube - Hide layout components:** Do not hide 'Show anyway' button in search results ([94fb367](94fb367618))
* **YouTube - Hide player components:** Show correct end video thumbnail if `Hide end screen suggested video` is enabled ([#4502](https://github.com/ReVanced/revanced-patches/issues/4502)) ([7cc939a](7cc939ab03))
* **YouTube - Hide video action buttons:** Move 'Disable Like and Subscribe glow' to action buttons settings menu ([7991c80](7991c80129))
* **YouTube - Return YouTube Dislike:** Use correct number formatting if using a different ReVanced language ([4ae1155](4ae1155e51))
* **YouTube - Spoof app version:** Force old settings menus if spoofing to older app targets ([#4490](https://github.com/ReVanced/revanced-patches/issues/4490)) ([0c0bbb8](0c0bbb8713))
* **YouTube - Spoof video streams:** Resolve playback issues with dynamic player config ([#4521](https://github.com/ReVanced/revanced-patches/issues/4521)) ([cbbf474](cbbf474c50))
* **YouTube - Swipe controls:** Adjust the overlay text size ([#4503](https://github.com/ReVanced/revanced-patches/issues/4503)) ([329f993](329f993024))
* **YouTube:** Do not hide player controls when using double tap to skip forward ([#4487](https://github.com/ReVanced/revanced-patches/issues/4487)) ([e664a24](e664a24f73))
* **YouTube:** Fix player button fade out animations ([#4469](https://github.com/ReVanced/revanced-patches/issues/4469)) ([a2c79f1](a2c79f1349))
* **YouTube:** Resolve button flickering when taping seekbar ([#4500](https://github.com/ReVanced/revanced-patches/issues/4500)) ([f5dd902](f5dd902915))

### Features

* **Infinity for Reddit:** Add support for Infinity for Reddit Plus ([#4511](https://github.com/ReVanced/revanced-patches/issues/4511)) ([fb8dbb4](fb8dbb4723))
* **NU.nl:** Add `Hide ads` and `Spoof Certificate` patch ([#4368](https://github.com/ReVanced/revanced-patches/issues/4368)) ([93ea250](93ea250bf3))
* **YouTube - Navigation buttons:** Add 'Hide notifications' setting ([#4485](https://github.com/ReVanced/revanced-patches/issues/4485)) ([d6eae01](d6eae01e12))
* **YouTube - Swipe controls:** Swipe controls UI improvements ([#4422](https://github.com/ReVanced/revanced-patches/issues/4422)) ([3548359](354835966d))
2025-03-03 07:01:16 +00:00
LisoUseInAIKyrios
cb609a6d9d chore: Merge branch dev to main (#4470) 2025-03-03 08:57:51 +02:00
github-actions[bot]
42e6de9e8f chore: Sync translations (#4525) 2025-03-03 08:55:55 +02:00
semantic-release-bot
c4a5b9a28c chore: Release v5.13.0-dev.19 [skip ci]
# [5.13.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.18...v5.13.0-dev.19) (2025-03-02)

### Bug Fixes

* **YouTube - Spoof video streams:** Resolve playback issues with dynamic player config ([#4521](https://github.com/ReVanced/revanced-patches/issues/4521)) ([cbbf474](cbbf474c50))
2025-03-02 15:44:18 +00:00
github-actions[bot]
c86c85947f chore: Sync translations (#4523) 2025-03-02 17:40:33 +02:00
LisoUseInAIKyrios
cbbf474c50 fix(YouTube - Spoof video streams): Resolve playback issues with dynamic player config (#4521) 2025-03-02 17:38:43 +02:00
semantic-release-bot
f147b7b73d chore: Release v5.13.0-dev.18 [skip ci]
# [5.13.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.17...v5.13.0-dev.18) (2025-02-28)

### Features

* **Infinity for Reddit:** Add support for Infinity for Reddit Plus ([#4511](https://github.com/ReVanced/revanced-patches/issues/4511)) ([fb8dbb4](fb8dbb4723))
2025-02-28 08:33:53 +00:00
tillcash
fb8dbb4723 feat(Infinity for Reddit): Add support for Infinity for Reddit Plus (#4511) 2025-02-28 10:31:02 +02:00
github-actions[bot]
1e0d27e689 chore: Sync translations (#4517) 2025-02-28 10:30:30 +02:00
semantic-release-bot
a2185bce09 chore: Release v5.13.0-dev.17 [skip ci]
# [5.13.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.16...v5.13.0-dev.17) (2025-02-27)

### Bug Fixes

* **YouTube - Hide filter bar:** Fix `Hide in feed` not working in subscriptions feed ([#4512](https://github.com/ReVanced/revanced-patches/issues/4512)) ([1b60a72](1b60a72ede))
2025-02-27 13:23:12 +00:00
ILoveOpenSourceApplications
1b60a72ede fix(YouTube - Hide filter bar): Fix Hide in feed not working in subscriptions feed (#4512) 2025-02-27 15:20:30 +02:00
semantic-release-bot
12b4ee04ad chore: Release v5.13.0-dev.16 [skip ci]
# [5.13.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.15...v5.13.0-dev.16) (2025-02-27)

### Features

* **NU.nl:** Add `Hide ads` and `Spoof Certificate` patch ([#4368](https://github.com/ReVanced/revanced-patches/issues/4368)) ([93ea250](93ea250bf3))
2025-02-27 06:12:28 +00:00
github-actions[bot]
f9a6cc96de chore: Sync translations (#4510) 2025-02-27 08:09:23 +02:00
Jasper Abbink
93ea250bf3 feat(NU.nl): Add Hide ads and Spoof Certificate patch (#4368)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2025-02-27 08:07:54 +02:00
semantic-release-bot
fdb946a2cc chore: Release v5.13.0-dev.15 [skip ci]
# [5.13.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.14...v5.13.0-dev.15) (2025-02-25)

### Bug Fixes

* **YouTube - Hide player components:** Show correct end video thumbnail if `Hide end screen suggested video` is enabled ([#4502](https://github.com/ReVanced/revanced-patches/issues/4502)) ([7cc939a](7cc939ab03))
2025-02-25 15:42:36 +00:00
ILoveOpenSourceApplications
7cc939ab03 fix(YouTube - Hide player components): Show correct end video thumbnail if Hide end screen suggested video is enabled (#4502)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-02-25 17:38:16 +02:00
github-actions[bot]
228d72428d chore: Sync translations (#4505) 2025-02-25 16:55:10 +02:00
semantic-release-bot
4db7ab4207 chore: Release v5.13.0-dev.14 [skip ci]
# [5.13.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.13...v5.13.0-dev.14) (2025-02-25)

### Bug Fixes

* **YouTube - Swipe controls:** Adjust the overlay text size ([#4503](https://github.com/ReVanced/revanced-patches/issues/4503)) ([329f993](329f993024))
2025-02-25 13:37:45 +00:00
MarcaD
329f993024 fix(YouTube - Swipe controls): Adjust the overlay text size (#4503) 2025-02-25 15:34:37 +02:00
github-actions[bot]
7cd1fb22d8 chore: Sync translations (#4504) 2025-02-25 15:34:19 +02:00
LisoUseInAIKyrios
ae111bc0b9 refactor(YouTube - Force original audio): Adjust settings text 2025-02-25 11:35:44 +02:00
semantic-release-bot
79f1dfd3e8 chore: Release v5.13.0-dev.13 [skip ci]
# [5.13.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.12...v5.13.0-dev.13) (2025-02-24)

### Bug Fixes

* **YouTube:** Resolve button flickering when taping seekbar ([#4500](https://github.com/ReVanced/revanced-patches/issues/4500)) ([f5dd902](f5dd902915))
2025-02-24 18:37:58 +00:00
MarcaD
f5dd902915 fix(YouTube): Resolve button flickering when taping seekbar (#4500) 2025-02-24 20:34:28 +02:00
semantic-release-bot
10e2b08eb2 chore: Release v5.13.0-dev.12 [skip ci]
# [5.13.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.11...v5.13.0-dev.12) (2025-02-24)

### Bug Fixes

* **YouTube - Return YouTube Dislike:** Use correct number formatting if using a different ReVanced language ([4ae1155](4ae1155e51))
2025-02-24 10:01:52 +00:00
LisoUseInAIKyrios
4ae1155e51 fix(YouTube - Return YouTube Dislike): Use correct number formatting if using a different ReVanced language 2025-02-24 11:58:57 +02:00
github-actions[bot]
69fbfaea19 chore: Sync translations (#4499)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2025-02-24 11:56:46 +02:00
semantic-release-bot
f44fede67c chore: Release v5.13.0-dev.11 [skip ci]
# [5.13.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.10...v5.13.0-dev.11) (2025-02-23)

### Bug Fixes

* **TikTok:** Resolve startup app crash ([3c52ab8](3c52ab8017))
2025-02-23 07:31:27 +00:00
LisoUseInAIKyrios
3c52ab8017 fix(TikTok): Resolve startup app crash 2025-02-23 09:28:37 +02:00
github-actions[bot]
d1641a6e3d chore: Sync translations (#4495) 2025-02-23 09:26:22 +02:00
semantic-release-bot
09773e8934 chore: Release v5.13.0-dev.10 [skip ci]
# [5.13.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.9...v5.13.0-dev.10) (2025-02-22)

### Bug Fixes

* **YouTube - Copy video URL:** Use correct button ordering ([d77d5bf](d77d5bfbdd))
2025-02-22 16:31:11 +00:00
LisoUseInAIKyrios
d77d5bfbdd fix(YouTube - Copy video URL): Use correct button ordering
Fixes refactoring oversight of button fade fix
2025-02-22 18:28:47 +02:00
semantic-release-bot
a84bded9e7 chore: Release v5.13.0-dev.9 [skip ci]
# [5.13.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.8...v5.13.0-dev.9) (2025-02-22)

### Bug Fixes

* **YouTube:** Do not hide player controls when using double tap to skip forward ([#4487](https://github.com/ReVanced/revanced-patches/issues/4487)) ([e664a24](e664a24f73))
2025-02-22 15:48:25 +00:00
LisoUseInAIKyrios
e664a24f73 fix(YouTube): Do not hide player controls when using double tap to skip forward (#4487)
Co-authored-by: MarcaDian <tolan.sheremeev@gmail.com>
2025-02-22 17:44:53 +02:00
semantic-release-bot
5bf964fff6 chore: Release v5.13.0-dev.8 [skip ci]
# [5.13.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.7...v5.13.0-dev.8) (2025-02-22)

### Bug Fixes

* **YouTube - Spoof app version:** Force old settings menus if spoofing to older app targets ([#4490](https://github.com/ReVanced/revanced-patches/issues/4490)) ([0c0bbb8](0c0bbb8713))
2025-02-22 09:43:04 +00:00
LisoUseInAIKyrios
0c0bbb8713 fix(YouTube - Spoof app version): Force old settings menus if spoofing to older app targets (#4490) 2025-02-22 11:40:06 +02:00
github-actions[bot]
8afe48cd92 chore: Sync translations (#4492) 2025-02-22 11:39:54 +02:00
github-actions[bot]
dde8ea31cb chore: Sync translations (#4491) 2025-02-22 11:34:59 +02:00
semantic-release-bot
d3abbe3e93 chore: Release v5.13.0-dev.7 [skip ci]
# [5.13.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.6...v5.13.0-dev.7) (2025-02-22)

### Bug Fixes

* **TikTok:** Resolve startup app crash ([c817977](c8179776ed))
2025-02-22 06:25:32 +00:00
LisoUseInAIKyrios
c8179776ed fix(TikTok): Resolve startup app crash 2025-02-22 08:20:19 +02:00
semantic-release-bot
c6c6516b12 chore: Release v5.13.0-dev.6 [skip ci]
# [5.13.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.5...v5.13.0-dev.6) (2025-02-21)

### Features

* **YouTube - Navigation buttons:** Add 'Hide notifications' setting ([#4485](https://github.com/ReVanced/revanced-patches/issues/4485)) ([d6eae01](d6eae01e12))
2025-02-21 12:13:01 +00:00
LisoUseInAIKyrios
d6eae01e12 feat(YouTube - Navigation buttons): Add 'Hide notifications' setting (#4485) 2025-02-21 14:09:01 +02:00
semantic-release-bot
ba88603f4b chore: Release v5.13.0-dev.5 [skip ci]
# [5.13.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.4...v5.13.0-dev.5) (2025-02-19)

### Bug Fixes

* **TikTok:** Resolve startup app crash ([d5aab3d](d5aab3d464))
2025-02-19 14:39:51 +00:00
LisoUseInAIKyrios
d5aab3d464 fix(TikTok): Resolve startup app crash 2025-02-19 16:36:07 +02:00
semantic-release-bot
fca2f70c0e chore: Release v5.13.0-dev.4 [skip ci]
# [5.13.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.3...v5.13.0-dev.4) (2025-02-19)

### Bug Fixes

* **TikTok:** Resolve startup app crash ([348f7e1](348f7e12cb))
2025-02-19 13:57:05 +00:00
LisoUseInAIKyrios
348f7e12cb fix(TikTok): Resolve startup app crash 2025-02-19 15:53:12 +02:00
semantic-release-bot
b6b7208eeb chore: Release v5.13.0-dev.3 [skip ci]
# [5.13.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.2...v5.13.0-dev.3) (2025-02-19)

### Bug Fixes

* **YouTube:** Fix player button fade out animations ([#4469](https://github.com/ReVanced/revanced-patches/issues/4469)) ([a2c79f1](a2c79f1349))
2025-02-19 11:25:29 +00:00
MarcaD
a2c79f1349 fix(YouTube): Fix player button fade out animations (#4469) 2025-02-19 13:22:46 +02:00
github-actions[bot]
4f5bb3c915 chore: Sync translations (#4478) 2025-02-19 13:22:17 +02:00
semantic-release-bot
4b77d27c77 chore: Release v5.13.0-dev.2 [skip ci]
# [5.13.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.1...v5.13.0-dev.2) (2025-02-18)

### Bug Fixes

* **YouTube - Hide video action buttons:** Move 'Disable Like and Subscribe glow' to action buttons settings menu ([7991c80](7991c80129))
2025-02-18 07:29:55 +00:00
LisoUseInAIKyrios
7991c80129 fix(YouTube - Hide video action buttons): Move 'Disable Like and Subscribe glow' to action buttons settings menu 2025-02-18 09:26:30 +02:00
github-actions[bot]
6baf4ea2ac chore: Sync translations (#4473) 2025-02-18 09:24:59 +02:00
semantic-release-bot
c89538c8f5 chore: Release v5.13.0-dev.1 [skip ci]
# [5.13.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.12.0...v5.13.0-dev.1) (2025-02-18)

### Bug Fixes

* **YouTube - Hide layout components:** Do not hide 'Show anyway' button in search results ([94fb367](94fb367618))

### Features

* **YouTube - Swipe controls:** Swipe controls UI improvements ([#4422](https://github.com/ReVanced/revanced-patches/issues/4422)) ([3548359](354835966d))
2025-02-18 07:11:45 +00:00
LisoUseInAIKyrios
94fb367618 fix(YouTube - Hide layout components): Do not hide 'Show anyway' button in search results 2025-02-18 09:08:37 +02:00
MarcaD
354835966d feat(YouTube - Swipe controls): Swipe controls UI improvements (#4422) 2025-02-18 09:07:28 +02:00
github-actions[bot]
168f9b769e chore: Sync translations (#4472) 2025-02-18 09:06:39 +02:00
ILoveOpenSourceApplications
e4c4b3a73a refactor(YouTube): Use more consistent strings (#4376) 2025-02-17 10:07:24 +02:00
semantic-release-bot
fce98b4960 chore: Release v5.12.0 [skip ci]
# [5.12.0](https://github.com/ReVanced/revanced-patches/compare/v5.11.0...v5.12.0) (2025-02-17)

### Bug Fixes

* Allow changing default settings for existing app installs ([#4464](https://github.com/ReVanced/revanced-patches/issues/4464)) ([a959d79](a959d798e8))
* **Windy.app:** Remove obsolete `Unlock pro` patch ([#4428](https://github.com/ReVanced/revanced-patches/issues/4428)) ([421af92](421af92f4c))
* **YouTube - Spoof video streams:** Change default client to `Android TV` ([#4465](https://github.com/ReVanced/revanced-patches/issues/4465)) ([04b37dd](04b37dd55a))
* **YouTube:** Remove obsolete 18.x targets ([#4454](https://github.com/ReVanced/revanced-patches/issues/4454)) ([92c38b2](92c38b2cb4))

### Features

* **Return YouTube Dislike:** add `Show estimated likes` setting ([#4443](https://github.com/ReVanced/revanced-patches/issues/4443)) ([7c4285e](7c4285e3e6))
* **YouTube - SponsorBlock:** Redesign skip buttons ([#4427](https://github.com/ReVanced/revanced-patches/issues/4427)) ([0079ece](0079eceb87))
* **YouTube Music:** Support version `8.05.50` ([#4439](https://github.com/ReVanced/revanced-patches/issues/4439)) ([bcd157d](bcd157dd2b))
* **YouTube Music:** Support version `8.05.51` ([2382e9d](2382e9d09e))
2025-02-17 06:23:44 +00:00
LisoUseInAIKyrios
839aa81e9c chore: Merge branch dev to main (#4437) 2025-02-17 08:20:23 +02:00
semantic-release-bot
905bb0ea5f chore: Release v5.12.0-dev.7 [skip ci]
# [5.12.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.6...v5.12.0-dev.7) (2025-02-16)

### Bug Fixes

* **YouTube - Spoof video streams:** Change default client to `Android TV` ([#4465](https://github.com/ReVanced/revanced-patches/issues/4465)) ([04b37dd](04b37dd55a))

### Features

* **YouTube Music:** Support version `8.05.51` ([2382e9d](2382e9d09e))
2025-02-16 16:41:43 +00:00
github-actions[bot]
a94a663859 chore: Sync translations (#4468) 2025-02-16 18:38:36 +02:00
LisoUseInAIKyrios
04b37dd55a fix(YouTube - Spoof video streams): Change default client to Android TV (#4465) 2025-02-16 18:34:12 +02:00
LisoUseInAIKyrios
2382e9d09e feat(YouTube Music): Support version 8.05.51 2025-02-16 18:31:52 +02:00
github-actions[bot]
97f504976a chore: Sync translations (#4467) 2025-02-16 18:30:04 +02:00
semantic-release-bot
0a6c5158e0 chore: Release v5.12.0-dev.6 [skip ci]
# [5.12.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.5...v5.12.0-dev.6) (2025-02-16)

### Bug Fixes

* Allow changing default settings for existing app installs ([#4464](https://github.com/ReVanced/revanced-patches/issues/4464)) ([a959d79](a959d798e8))
2025-02-16 13:19:31 +00:00
LisoUseInAIKyrios
a959d798e8 fix: Allow changing default settings for existing app installs (#4464) 2025-02-16 15:16:24 +02:00
semantic-release-bot
39a0b9bda6 chore: Release v5.12.0-dev.5 [skip ci]
# [5.12.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.4...v5.12.0-dev.5) (2025-02-13)

### Bug Fixes

* **YouTube:** Remove obsolete 18.x targets ([#4454](https://github.com/ReVanced/revanced-patches/issues/4454)) ([92c38b2](92c38b2cb4))
2025-02-13 12:41:16 +00:00
LisoUseInAIKyrios
92c38b2cb4 fix(YouTube): Remove obsolete 18.x targets (#4454) 2025-02-13 14:38:23 +02:00
github-actions[bot]
4732210d4b chore: Sync translations (#4455) 2025-02-13 14:35:32 +02:00
semantic-release-bot
f30a49f1cb chore: Release v5.12.0-dev.4 [skip ci]
# [5.12.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.3...v5.12.0-dev.4) (2025-02-11)

### Features

* **YouTube Music:** Support version `8.05.50` ([#4439](https://github.com/ReVanced/revanced-patches/issues/4439)) ([bcd157d](bcd157dd2b))
2025-02-11 18:40:51 +00:00
Alberto Ponces
bcd157dd2b feat(YouTube Music): Support version 8.05.50 (#4439) 2025-02-11 20:37:58 +02:00
LisoUseInAIKyrios
d299ea5973 chore(deps): Remove unused dependency 2025-02-11 17:41:40 +02:00
dependabot[bot]
a20021e290 chore(deps): bump com.google.code.gson:gson from 2.11.0 to 2.12.1 (#4398)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-11 17:19:52 +02:00
dependabot[bot]
373ca966f3 chore(deps): Bump com.google.guava:guava from 33.2.1-jre to 33.4.0-jre (#4252) 2025-02-11 17:17:35 +02:00
semantic-release-bot
12de922afa chore: Release v5.12.0-dev.3 [skip ci]
# [5.12.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.2...v5.12.0-dev.3) (2025-02-11)

### Bug Fixes

* **Windy.app:** Remove obsolete `Unlock pro` patch ([#4428](https://github.com/ReVanced/revanced-patches/issues/4428)) ([421af92](421af92f4c))
2025-02-11 15:11:34 +00:00
dependabot[bot]
580bb3cf6c chore(deps-dev): bump semantic-release from 24.1.2 to 24.2.1 (#4397) 2025-02-11 17:07:28 +02:00
LisoUseInAIKyrios
421af92f4c fix(Windy.app): Remove obsolete Unlock pro patch (#4428) 2025-02-11 17:05:46 +02:00
github-actions[bot]
4d03e1b5a1 chore: Sync translations (#4446) 2025-02-11 17:05:17 +02:00
LisoUseInAIKyrios
24d68df6cd refactor: Improve XML performance 2025-02-11 15:24:29 +02:00
semantic-release-bot
e9aee17746 chore: Release v5.12.0-dev.2 [skip ci]
# [5.12.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.1...v5.12.0-dev.2) (2025-02-11)

### Features

* **Return YouTube Dislike:** add `Show estimated likes` setting ([#4443](https://github.com/ReVanced/revanced-patches/issues/4443)) ([7c4285e](7c4285e3e6))
2025-02-11 10:15:24 +00:00
LisoUseInAIKyrios
7c4285e3e6 feat(Return YouTube Dislike): add Show estimated likes setting (#4443) 2025-02-11 12:12:24 +02:00
semantic-release-bot
e3110271a7 chore: Release v5.12.0-dev.1 [skip ci]
# [5.12.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.11.0...v5.12.0-dev.1) (2025-02-10)

### Features

* **YouTube - SponsorBlock:** Redesign skip buttons ([#4427](https://github.com/ReVanced/revanced-patches/issues/4427)) ([0079ece](0079eceb87))
2025-02-10 18:33:12 +00:00
MarcaD
0079eceb87 feat(YouTube - SponsorBlock): Redesign skip buttons (#4427)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
2025-02-10 20:30:06 +02:00
github-actions[bot]
af2a97cb16 chore: Sync translations (#4436) 2025-02-10 20:29:28 +02:00
semantic-release-bot
aeb552e8f2 chore: Release v5.11.0 [skip ci]
# [5.11.0](https://github.com/ReVanced/revanced-patches/compare/v5.10.0...v5.11.0) (2025-02-07)

### Bug Fixes

* Fix broken `Remove screen capture restriction`,  `Remove screenshot restriction`, `Spoof Wi-Fi connection`, and `Export internal data documents provider` patch ([#4405](https://github.com/ReVanced/revanced-patches/issues/4405)) ([399889c](399889c6fa))
* **YouTube - Enable slide to seek:** Change patch to default include ([76fd33c](76fd33ca54))
* **YouTube - Hide layout components:** Hide new type of community post ([#4404](https://github.com/ReVanced/revanced-patches/issues/4404)) ([a06c031](a06c0318bf))
* **YouTube - Theme:** Use custom seekbar color for cairo startup animation ([#4399](https://github.com/ReVanced/revanced-patches/issues/4399)) ([f81b658](f81b658fb7))

### Features

* **YouTube - Change start page:** Add additional start pages ([#4413](https://github.com/ReVanced/revanced-patches/issues/4413)) ([b7ebfdd](b7ebfddf65))
2025-02-07 07:05:36 +00:00
LisoUseInAIKyrios
6e936fea42 chore: Merge branch dev to main (#4388) 2025-02-07 09:02:22 +02:00
github-actions[bot]
f63769f39f chore: Sync translations (#4421) 2025-02-07 08:58:04 +02:00
semantic-release-bot
1c9ab20a63 chore: Release v5.11.0-dev.2 [skip ci]
# [5.11.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.11.0-dev.1...v5.11.0-dev.2) (2025-02-06)

### Bug Fixes

* Fix broken `Remove screen capture restriction`,  `Remove screenshot restriction`, `Spoof Wi-Fi connection`, and `Export internal data documents provider` patch ([#4405](https://github.com/ReVanced/revanced-patches/issues/4405)) ([399889c](399889c6fa))
2025-02-06 12:29:29 +00:00
github-actions[bot]
cdeccad908 chore: Sync translations (#4417) 2025-02-06 14:25:50 +02:00
LisoUseInAIKyrios
399889c6fa fix: Fix broken Remove screen capture restriction, Remove screenshot restriction, Spoof Wi-Fi connection, and Export internal data documents provider patch (#4405) 2025-02-06 14:24:40 +02:00
github-actions[bot]
ec77861410 chore: Sync translations (#4415) 2025-02-05 20:42:07 +02:00
semantic-release-bot
b5afc6d827 chore: Release v5.11.0-dev.1 [skip ci]
# [5.11.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.3...v5.11.0-dev.1) (2025-02-05)

### Features

* **YouTube - Change start page:** Add additional start pages ([#4413](https://github.com/ReVanced/revanced-patches/issues/4413)) ([b7ebfdd](b7ebfddf65))
2025-02-05 18:36:04 +00:00
LisoUseInAIKyrios
b7ebfddf65 feat(YouTube - Change start page): Add additional start pages (#4413) 2025-02-05 20:32:42 +02:00
github-actions[bot]
2742aca48b chore: Sync translations (#4414) 2025-02-05 20:32:21 +02:00
semantic-release-bot
14ca4d3288 chore: Release v5.10.1-dev.3 [skip ci]
## [5.10.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.2...v5.10.1-dev.3) (2025-02-03)

### Bug Fixes

* **YouTube - Hide layout components:** Hide new type of community post ([#4404](https://github.com/ReVanced/revanced-patches/issues/4404)) ([a06c031](a06c0318bf))
2025-02-03 10:15:53 +00:00
ILoveOpenSourceApplications
a06c0318bf fix(YouTube - Hide layout components): Hide new type of community post (#4404) 2025-02-03 12:13:15 +02:00
semantic-release-bot
7f9f668435 chore: Release v5.10.1-dev.2 [skip ci]
## [5.10.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.1...v5.10.1-dev.2) (2025-02-03)

### Bug Fixes

* **YouTube - Enable slide to seek:** Change patch to default include ([76fd33c](76fd33ca54))
2025-02-03 10:11:28 +00:00
LisoUseInAIKyrios
76fd33ca54 fix(YouTube - Enable slide to seek): Change patch to default include 2025-02-03 12:08:28 +02:00
semantic-release-bot
9a653e9c5a chore: Release v5.10.1-dev.1 [skip ci]
## [5.10.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.10.0...v5.10.1-dev.1) (2025-02-02)

### Bug Fixes

* **YouTube - Theme:** Use custom seekbar color for cairo startup animation ([#4399](https://github.com/ReVanced/revanced-patches/issues/4399)) ([f81b658](f81b658fb7))
2025-02-02 09:14:21 +00:00
LisoUseInAIKyrios
f81b658fb7 fix(YouTube - Theme): Use custom seekbar color for cairo startup animation (#4399) 2025-02-02 11:10:57 +02:00
LisoUseInAIKyrios
7ff39d89d6 refactor(YouTube - Spoof app version): Use more concise description of 19.26.42 2025-01-31 12:51:49 +02:00
LisoUseInAIKyrios
78ab0ec2bd refactor(YouTube - Swipe controls): Use more consistent settings language of 'opacity' and 0-100 scale 2025-01-31 12:40:45 +02:00
semantic-release-bot
3ab67f1539 chore: Release v5.10.0 [skip ci]
# [5.10.0](https://github.com/ReVanced/revanced-patches/compare/v5.9.0...v5.10.0) (2025-01-31)

### Bug Fixes

* **SwissId - Play integrity Removal:** Add recommended app version ([#4370](https://github.com/ReVanced/revanced-patches/issues/4370)) ([6fa2dee](6fa2deea69))
* Use correct path to fix invalid file paths ([043ebbb](043ebbb6d4))
* **YouTube - Hide ads:** fix 'Hide the Visit store button on channel pages' not working ([#4364](https://github.com/ReVanced/revanced-patches/issues/4364)) ([a73db03](a73db03671))
* **YouTube - Hide Ads:** Hide end screen store banner without leaving empty space ([#4367](https://github.com/ReVanced/revanced-patches/issues/4367)) ([aaeee4a](aaeee4a895))
* **YouTube - Hide ads:** Hide new types of tablet ads ([f844a1c](f844a1cd76))
* **YouTube - Hide layout components:** Hide new kind of community post ([#4341](https://github.com/ReVanced/revanced-patches/issues/4341)) ([6721a28](6721a284cd))
* **YouTube - Hide seekbar:** Do not hide player seekbar if hide feed seekbar is enabled ([#4333](https://github.com/ReVanced/revanced-patches/issues/4333)) ([7c8efca](7c8efcaf41))
* **YouTube - Hide video description components:** Use correct string key names ([64cdce2](64cdce28a6))
* **YouTube - Spoof video streams:** Update settings side effects summary text ([#4369](https://github.com/ReVanced/revanced-patches/issues/4369)) ([6802529](680252967e))
* **YouTube - Theme:** Fix 19.25 - 19.45 patch error ([df2d070](df2d070a43))
* **YouTube - Theme:** Replace custom seekbar gradient colors instead of disabling ([#4329](https://github.com/ReVanced/revanced-patches/issues/4329)) ([f4989ed](f4989ed0a5))

### Features

* **YouTube - Hide ads:** Add `Hide end screen store banner` ([#4351](https://github.com/ReVanced/revanced-patches/issues/4351)) ([76bbd7e](76bbd7ed2f))
* **YouTube - Hide video description components:** Add `Hide How this content was made section` ([#4355](https://github.com/ReVanced/revanced-patches/issues/4355)) ([a72404e](a72404eeab))
* **YouTube - Theme:** Add option to use custom seekbar accent color ([#4337](https://github.com/ReVanced/revanced-patches/issues/4337)) ([8104bbd](8104bbd7d7))
* **YouTube:** Add patch `Disable HDR video` ([#4347](https://github.com/ReVanced/revanced-patches/issues/4347)) ([1d12c41](1d12c4156d))
2025-01-31 09:18:31 +00:00
LisoUseInAIKyrios
8652cd613f chore: Merge branch dev to main (#4330) 2025-01-31 11:15:01 +02:00
github-actions[bot]
bc8388713c chore: Sync translations (#4386) 2025-01-31 11:14:34 +02:00
github-actions[bot]
d4b2e3be3e chore: Sync translations (#4385) 2025-01-31 10:55:59 +02:00
LisoUseInAIKyrios
57c48b7829 ci: Fix Crowdin pull 2025-01-31 10:51:42 +02:00
LisoUseInAIKyrios
aaa7523ee4 chore: Add translatable string tags 2025-01-31 10:03:31 +02:00
LisoUseInAIKyrios
785df4fe69 ci: Preprocess strings before pushing to Crowdin (#4383) 2025-01-31 09:58:26 +02:00
github-actions[bot]
83208eb50d chore: Sync translations (#4382) 2025-01-30 09:36:37 +02:00
github-actions[bot]
9437db11eb chore: Sync translations (#4381) 2025-01-30 09:32:21 +02:00
github-actions[bot]
1843c8bf70 chore: Sync translations (#4380) 2025-01-30 09:27:20 +02:00
LisoUseInAIKyrios
778b51fbff ci: Fix Crowdin cron pull strings? 2025-01-30 09:25:20 +02:00
github-actions[bot]
ee0fdcdf86 chore: Sync translations (#4379) 2025-01-30 09:18:15 +02:00
semantic-release-bot
57cc73d9c4 chore: Release v5.10.0-dev.11 [skip ci]
# [5.10.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.10...v5.10.0-dev.11) (2025-01-30)

### Bug Fixes

* Use correct path to fix invalid file paths ([043ebbb](043ebbb6d4))
2025-01-30 00:58:00 +00:00
oSumAtrIX
043ebbb6d4 fix: Use correct path to fix invalid file paths 2025-01-30 01:53:44 +01:00
semantic-release-bot
d5551923fc chore: Release v5.10.0-dev.10 [skip ci]
# [5.10.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.9...v5.10.0-dev.10) (2025-01-29)

### Bug Fixes

* **YouTube - Hide ads:** Hide new types of tablet ads ([f844a1c](f844a1cd76))
2025-01-29 18:57:01 +00:00
LisoUseInAIKyrios
f844a1cd76 fix(YouTube - Hide ads): Hide new types of tablet ads 2025-01-29 20:52:57 +02:00
semantic-release-bot
a7e3277cc1 chore: Release v5.10.0-dev.9 [skip ci]
# [5.10.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.8...v5.10.0-dev.9) (2025-01-29)

### Bug Fixes

* **SwissId - Play integrity Removal:** Add recommended app version ([#4370](https://github.com/ReVanced/revanced-patches/issues/4370)) ([6fa2dee](6fa2deea69))
2025-01-29 17:47:51 +00:00
Corentin C
6fa2deea69 fix(SwissId - Play integrity Removal): Add recommended app version (#4370) 2025-01-29 19:44:27 +02:00
github-actions[bot]
dcca2a3697 chore: Sync translations (#4374) 2025-01-29 19:43:28 +02:00
semantic-release-bot
018160fd9c chore: Release v5.10.0-dev.8 [skip ci]
# [5.10.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.7...v5.10.0-dev.8) (2025-01-29)

### Bug Fixes

* **YouTube - Spoof video streams:** Update settings side effects summary text ([#4369](https://github.com/ReVanced/revanced-patches/issues/4369)) ([6802529](680252967e))
2025-01-29 14:04:01 +00:00
LisoUseInAIKyrios
680252967e fix(YouTube - Spoof video streams): Update settings side effects summary text (#4369) 2025-01-29 16:00:22 +02:00
semantic-release-bot
e79eba81d9 chore: Release v5.10.0-dev.7 [skip ci]
# [5.10.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.6...v5.10.0-dev.7) (2025-01-29)

### Bug Fixes

* **YouTube - Hide ads:** fix 'Hide the Visit store button on channel pages' not working ([#4364](https://github.com/ReVanced/revanced-patches/issues/4364)) ([a73db03](a73db03671))
2025-01-29 08:31:07 +00:00
ILoveOpenSourceApplications
a73db03671 fix(YouTube - Hide ads): fix 'Hide the Visit store button on channel pages' not working (#4364) 2025-01-29 10:28:26 +02:00
semantic-release-bot
055ad04281 chore: Release v5.10.0-dev.6 [skip ci]
# [5.10.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.5...v5.10.0-dev.6) (2025-01-29)

### Bug Fixes

* **YouTube - Hide Ads:** Hide end screen store banner without leaving empty space ([#4367](https://github.com/ReVanced/revanced-patches/issues/4367)) ([aaeee4a](aaeee4a895))
2025-01-29 07:44:09 +00:00
LisoUseInAIKyrios
aaeee4a895 fix(YouTube - Hide Ads): Hide end screen store banner without leaving empty space (#4367) 2025-01-29 09:40:59 +02:00
semantic-release-bot
654b339f66 chore: Release v5.10.0-dev.5 [skip ci]
# [5.10.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.4...v5.10.0-dev.5) (2025-01-27)

### Bug Fixes

* **YouTube - Hide video description components:** Use correct string key names ([64cdce2](64cdce28a6))
2025-01-27 15:01:10 +00:00
LisoUseInAIKyrios
64cdce28a6 fix(YouTube - Hide video description components): Use correct string key names 2025-01-27 16:58:44 +02:00
326 changed files with 15618 additions and 10480 deletions

View File

@@ -26,6 +26,7 @@ jobs:
config: crowdin.yml
upload_sources: false
download_translations: true
skip_ref_checkout: true
localization_branch_name: feat/translations
create_pull_request: false
env:

View File

@@ -18,6 +18,11 @@ jobs:
with:
fetch-depth: 0
- name: Preprocess strings
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew clean preprocessCrowdinStrings
- name: Push strings
uses: crowdin/github-action@v2
with:

View File

@@ -1,3 +1,409 @@
# [5.14.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.4...v5.14.0-dev.5) (2025-03-06)
### Features
* **Infinity for Reddit:** Add support for package name on IzzyOnDroid ([#4554](https://github.com/ReVanced/revanced-patches/issues/4554)) ([cf9f959](https://github.com/ReVanced/revanced-patches/commit/cf9f959923076c10a7f0a29f6ba277f5a055ec07))
# [5.14.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.3...v5.14.0-dev.4) (2025-03-06)
### Bug Fixes
* **YouTube:** Combine `Restore old video quality menu` and `Remember video quality` into `Video quality` patch ([#4552](https://github.com/ReVanced/revanced-patches/issues/4552)) ([ee67b76](https://github.com/ReVanced/revanced-patches/commit/ee67b763d5c5947a5b1ef4420b1efa820ed6af83))
# [5.14.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.2...v5.14.0-dev.3) (2025-03-06)
### Bug Fixes
* **Boost for reddit - Client spoof:** Use a different user agent to combat Reddit's API issues ([5d3c817](https://github.com/ReVanced/revanced-patches/commit/5d3c8175b34a3f6ae2732b25db0851773a8c000d))
# [5.14.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.14.0-dev.1...v5.14.0-dev.2) (2025-03-06)
### Bug Fixes
* **YouTube - Hide ads:** Hide new type of buttoned ad ([#4528](https://github.com/ReVanced/revanced-patches/issues/4528)) ([4387a7b](https://github.com/ReVanced/revanced-patches/commit/4387a7b131f49729e902e008bb4cec073635c040))
# [5.14.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.1-dev.1...v5.14.0-dev.1) (2025-03-06)
### Features
* **YouTube - Remember video quality:** Add separate Shorts default quality settings ([#4543](https://github.com/ReVanced/revanced-patches/issues/4543)) ([88142ab](https://github.com/ReVanced/revanced-patches/commit/88142ab464192b564b1b8d56a6b45663f77f5e00))
## [5.13.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.13.0...v5.13.1-dev.1) (2025-03-06)
### Bug Fixes
* **YouTube - Change form factor:** Restore Automotive form factor watch history menu, channel pages, and community posts ([#4541](https://github.com/ReVanced/revanced-patches/issues/4541)) ([aa5c001](https://github.com/ReVanced/revanced-patches/commit/aa5c001968446e5270c756256724e917009612cd))
# [5.13.0](https://github.com/ReVanced/revanced-patches/compare/v5.12.0...v5.13.0) (2025-03-03)
### Bug Fixes
* **TikTok:** Resolve startup app crash ([18c0fc2](https://github.com/ReVanced/revanced-patches/commit/18c0fc2a7f186f50a904fd25dbaa739abdd24993))
* **TikTok:** Resolve startup app crash ([6466398](https://github.com/ReVanced/revanced-patches/commit/64663983b84de1f28636205f61bf0a24c83968d1))
* **TikTok:** Resolve startup app crash ([c14bc24](https://github.com/ReVanced/revanced-patches/commit/c14bc244550de30eca975ca7c09e8eb0c47534b5))
* **TikTok:** Resolve startup app crash ([d700076](https://github.com/ReVanced/revanced-patches/commit/d7000768a5e5a688c9f4e48858ac34e352222c1e))
* **YouTube - Copy video URL:** Use correct button ordering ([5e622cc](https://github.com/ReVanced/revanced-patches/commit/5e622ccf66d34af31c6026fa7f4d332460c6ecb0))
* **YouTube - Hide filter bar:** Fix `Hide in feed` not working in subscriptions feed ([#4512](https://github.com/ReVanced/revanced-patches/issues/4512)) ([634d0ee](https://github.com/ReVanced/revanced-patches/commit/634d0ee12e31491c7ee1d4ceb002daf8366a3c15))
* **YouTube - Hide layout components:** Do not hide 'Show anyway' button in search results ([4ac8854](https://github.com/ReVanced/revanced-patches/commit/4ac8854b99808a8957f3b0b7438e1e0cdedffbaf))
* **YouTube - Hide player components:** Show correct end video thumbnail if `Hide end screen suggested video` is enabled ([#4502](https://github.com/ReVanced/revanced-patches/issues/4502)) ([6c4885a](https://github.com/ReVanced/revanced-patches/commit/6c4885a1d5dfff50100b01840b5552d92e83ee4a))
* **YouTube - Hide video action buttons:** Move 'Disable Like and Subscribe glow' to action buttons settings menu ([29b265d](https://github.com/ReVanced/revanced-patches/commit/29b265d8fdaa48502650be9623bfc518a57a0bb1))
* **YouTube - Return YouTube Dislike:** Use correct number formatting if using a different ReVanced language ([edf66f4](https://github.com/ReVanced/revanced-patches/commit/edf66f4e16d46156cb8b8e31d18cb8dbcb87737e))
* **YouTube - Spoof app version:** Force old settings menus if spoofing to older app targets ([#4490](https://github.com/ReVanced/revanced-patches/issues/4490)) ([45e7c46](https://github.com/ReVanced/revanced-patches/commit/45e7c46dd9c70c926b8b1a97ada668f90f5f6f8c))
* **YouTube - Spoof video streams:** Resolve playback issues with dynamic player config ([#4521](https://github.com/ReVanced/revanced-patches/issues/4521)) ([647e764](https://github.com/ReVanced/revanced-patches/commit/647e7642efc0c00db17ccb6a620d1c96ccf4afed))
* **YouTube - Swipe controls:** Adjust the overlay text size ([#4503](https://github.com/ReVanced/revanced-patches/issues/4503)) ([6dc4bf7](https://github.com/ReVanced/revanced-patches/commit/6dc4bf75e09ed6f05534919d7b769b720043abce))
* **YouTube:** Do not hide player controls when using double tap to skip forward ([#4487](https://github.com/ReVanced/revanced-patches/issues/4487)) ([63fe870](https://github.com/ReVanced/revanced-patches/commit/63fe870d48ca2217327b952bde241b7f16ced850))
* **YouTube:** Fix player button fade out animations ([#4469](https://github.com/ReVanced/revanced-patches/issues/4469)) ([bf8e775](https://github.com/ReVanced/revanced-patches/commit/bf8e7759f9bdbdfef419a879fb3dd7cf0dff0098))
* **YouTube:** Resolve button flickering when taping seekbar ([#4500](https://github.com/ReVanced/revanced-patches/issues/4500)) ([1f08047](https://github.com/ReVanced/revanced-patches/commit/1f08047b48cc9555a4887d16ec7219a55a77251f))
### Features
* **Infinity for Reddit:** Add support for Infinity for Reddit Plus ([#4511](https://github.com/ReVanced/revanced-patches/issues/4511)) ([d74732b](https://github.com/ReVanced/revanced-patches/commit/d74732b7596104321bde263201d95649e4bd0eee))
* **NU.nl:** Add `Hide ads` and `Spoof Certificate` patch ([#4368](https://github.com/ReVanced/revanced-patches/issues/4368)) ([f3268fb](https://github.com/ReVanced/revanced-patches/commit/f3268fb03ca25fb5465e36015b6c9dec2c84a655))
* **YouTube - Navigation buttons:** Add 'Hide notifications' setting ([#4485](https://github.com/ReVanced/revanced-patches/issues/4485)) ([506d241](https://github.com/ReVanced/revanced-patches/commit/506d2414bbc760e764e5a514b32926083d6ecb6b))
* **YouTube - Swipe controls:** Swipe controls UI improvements ([#4422](https://github.com/ReVanced/revanced-patches/issues/4422)) ([198e4d2](https://github.com/ReVanced/revanced-patches/commit/198e4d2a2315c24a09eb9ecfefbd131a75384d2c))
# [5.13.0-dev.19](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.18...v5.13.0-dev.19) (2025-03-02)
### Bug Fixes
* **YouTube - Spoof video streams:** Resolve playback issues with dynamic player config ([#4521](https://github.com/ReVanced/revanced-patches/issues/4521)) ([647e764](https://github.com/ReVanced/revanced-patches/commit/647e7642efc0c00db17ccb6a620d1c96ccf4afed))
# [5.13.0-dev.18](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.17...v5.13.0-dev.18) (2025-02-28)
### Features
* **Infinity for Reddit:** Add support for Infinity for Reddit Plus ([#4511](https://github.com/ReVanced/revanced-patches/issues/4511)) ([d74732b](https://github.com/ReVanced/revanced-patches/commit/d74732b7596104321bde263201d95649e4bd0eee))
# [5.13.0-dev.17](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.16...v5.13.0-dev.17) (2025-02-27)
### Bug Fixes
* **YouTube - Hide filter bar:** Fix `Hide in feed` not working in subscriptions feed ([#4512](https://github.com/ReVanced/revanced-patches/issues/4512)) ([634d0ee](https://github.com/ReVanced/revanced-patches/commit/634d0ee12e31491c7ee1d4ceb002daf8366a3c15))
# [5.13.0-dev.16](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.15...v5.13.0-dev.16) (2025-02-27)
### Features
* **NU.nl:** Add `Hide ads` and `Spoof Certificate` patch ([#4368](https://github.com/ReVanced/revanced-patches/issues/4368)) ([f3268fb](https://github.com/ReVanced/revanced-patches/commit/f3268fb03ca25fb5465e36015b6c9dec2c84a655))
# [5.13.0-dev.15](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.14...v5.13.0-dev.15) (2025-02-25)
### Bug Fixes
* **YouTube - Hide player components:** Show correct end video thumbnail if `Hide end screen suggested video` is enabled ([#4502](https://github.com/ReVanced/revanced-patches/issues/4502)) ([6c4885a](https://github.com/ReVanced/revanced-patches/commit/6c4885a1d5dfff50100b01840b5552d92e83ee4a))
# [5.13.0-dev.14](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.13...v5.13.0-dev.14) (2025-02-25)
### Bug Fixes
* **YouTube - Swipe controls:** Adjust the overlay text size ([#4503](https://github.com/ReVanced/revanced-patches/issues/4503)) ([6dc4bf7](https://github.com/ReVanced/revanced-patches/commit/6dc4bf75e09ed6f05534919d7b769b720043abce))
# [5.13.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.12...v5.13.0-dev.13) (2025-02-24)
### Bug Fixes
* **YouTube:** Resolve button flickering when taping seekbar ([#4500](https://github.com/ReVanced/revanced-patches/issues/4500)) ([1f08047](https://github.com/ReVanced/revanced-patches/commit/1f08047b48cc9555a4887d16ec7219a55a77251f))
# [5.13.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.11...v5.13.0-dev.12) (2025-02-24)
### Bug Fixes
* **YouTube - Return YouTube Dislike:** Use correct number formatting if using a different ReVanced language ([edf66f4](https://github.com/ReVanced/revanced-patches/commit/edf66f4e16d46156cb8b8e31d18cb8dbcb87737e))
# [5.13.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.10...v5.13.0-dev.11) (2025-02-23)
### Bug Fixes
* **TikTok:** Resolve startup app crash ([18c0fc2](https://github.com/ReVanced/revanced-patches/commit/18c0fc2a7f186f50a904fd25dbaa739abdd24993))
# [5.13.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.9...v5.13.0-dev.10) (2025-02-22)
### Bug Fixes
* **YouTube - Copy video URL:** Use correct button ordering ([5e622cc](https://github.com/ReVanced/revanced-patches/commit/5e622ccf66d34af31c6026fa7f4d332460c6ecb0))
# [5.13.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.8...v5.13.0-dev.9) (2025-02-22)
### Bug Fixes
* **YouTube:** Do not hide player controls when using double tap to skip forward ([#4487](https://github.com/ReVanced/revanced-patches/issues/4487)) ([63fe870](https://github.com/ReVanced/revanced-patches/commit/63fe870d48ca2217327b952bde241b7f16ced850))
# [5.13.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.7...v5.13.0-dev.8) (2025-02-22)
### Bug Fixes
* **YouTube - Spoof app version:** Force old settings menus if spoofing to older app targets ([#4490](https://github.com/ReVanced/revanced-patches/issues/4490)) ([45e7c46](https://github.com/ReVanced/revanced-patches/commit/45e7c46dd9c70c926b8b1a97ada668f90f5f6f8c))
# [5.13.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.6...v5.13.0-dev.7) (2025-02-22)
### Bug Fixes
* **TikTok:** Resolve startup app crash ([6466398](https://github.com/ReVanced/revanced-patches/commit/64663983b84de1f28636205f61bf0a24c83968d1))
# [5.13.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.5...v5.13.0-dev.6) (2025-02-21)
### Features
* **YouTube - Navigation buttons:** Add 'Hide notifications' setting ([#4485](https://github.com/ReVanced/revanced-patches/issues/4485)) ([506d241](https://github.com/ReVanced/revanced-patches/commit/506d2414bbc760e764e5a514b32926083d6ecb6b))
# [5.13.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.4...v5.13.0-dev.5) (2025-02-19)
### Bug Fixes
* **TikTok:** Resolve startup app crash ([c14bc24](https://github.com/ReVanced/revanced-patches/commit/c14bc244550de30eca975ca7c09e8eb0c47534b5))
# [5.13.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.3...v5.13.0-dev.4) (2025-02-19)
### Bug Fixes
* **TikTok:** Resolve startup app crash ([d700076](https://github.com/ReVanced/revanced-patches/commit/d7000768a5e5a688c9f4e48858ac34e352222c1e))
# [5.13.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.2...v5.13.0-dev.3) (2025-02-19)
### Bug Fixes
* **YouTube:** Fix player button fade out animations ([#4469](https://github.com/ReVanced/revanced-patches/issues/4469)) ([bf8e775](https://github.com/ReVanced/revanced-patches/commit/bf8e7759f9bdbdfef419a879fb3dd7cf0dff0098))
# [5.13.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.13.0-dev.1...v5.13.0-dev.2) (2025-02-18)
### Bug Fixes
* **YouTube - Hide video action buttons:** Move 'Disable Like and Subscribe glow' to action buttons settings menu ([29b265d](https://github.com/ReVanced/revanced-patches/commit/29b265d8fdaa48502650be9623bfc518a57a0bb1))
# [5.13.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.12.0...v5.13.0-dev.1) (2025-02-18)
### Bug Fixes
* **YouTube - Hide layout components:** Do not hide 'Show anyway' button in search results ([4ac8854](https://github.com/ReVanced/revanced-patches/commit/4ac8854b99808a8957f3b0b7438e1e0cdedffbaf))
### Features
* **YouTube - Swipe controls:** Swipe controls UI improvements ([#4422](https://github.com/ReVanced/revanced-patches/issues/4422)) ([198e4d2](https://github.com/ReVanced/revanced-patches/commit/198e4d2a2315c24a09eb9ecfefbd131a75384d2c))
# [5.12.0](https://github.com/ReVanced/revanced-patches/compare/v5.11.0...v5.12.0) (2025-02-17)
### Bug Fixes
* Allow changing default settings for existing app installs ([#4464](https://github.com/ReVanced/revanced-patches/issues/4464)) ([1bd7986](https://github.com/ReVanced/revanced-patches/commit/1bd7986823e774a929c8a9102a7cc96e245d5274))
* **Windy.app:** Remove obsolete `Unlock pro` patch ([#4428](https://github.com/ReVanced/revanced-patches/issues/4428)) ([83d116e](https://github.com/ReVanced/revanced-patches/commit/83d116e8fd3935ee431cfdf0b8e095d04ee77259))
* **YouTube - Spoof video streams:** Change default client to `Android TV` ([#4465](https://github.com/ReVanced/revanced-patches/issues/4465)) ([0412c79](https://github.com/ReVanced/revanced-patches/commit/0412c7901dc8599b6079d9c3ba26452f88af642b))
* **YouTube:** Remove obsolete 18.x targets ([#4454](https://github.com/ReVanced/revanced-patches/issues/4454)) ([a006758](https://github.com/ReVanced/revanced-patches/commit/a0067581d0f877e1b4eb1f888a25786f09676b2e))
### Features
* **Return YouTube Dislike:** add `Show estimated likes` setting ([#4443](https://github.com/ReVanced/revanced-patches/issues/4443)) ([9a88b42](https://github.com/ReVanced/revanced-patches/commit/9a88b4239fd63d5f91105fec8e7d59d318a5d09a))
* **YouTube - SponsorBlock:** Redesign skip buttons ([#4427](https://github.com/ReVanced/revanced-patches/issues/4427)) ([8f4883f](https://github.com/ReVanced/revanced-patches/commit/8f4883fc002420bfb4056401e23445c99e1d3fce))
* **YouTube Music:** Support version `8.05.50` ([#4439](https://github.com/ReVanced/revanced-patches/issues/4439)) ([b31fed9](https://github.com/ReVanced/revanced-patches/commit/b31fed98901fcda1bce6f05eb0de63280c689fa0))
* **YouTube Music:** Support version `8.05.51` ([128441e](https://github.com/ReVanced/revanced-patches/commit/128441e78bc0d096c3fc2f57782ab90c39c3ae4b))
# [5.12.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.6...v5.12.0-dev.7) (2025-02-16)
### Bug Fixes
* **YouTube - Spoof video streams:** Change default client to `Android TV` ([#4465](https://github.com/ReVanced/revanced-patches/issues/4465)) ([0412c79](https://github.com/ReVanced/revanced-patches/commit/0412c7901dc8599b6079d9c3ba26452f88af642b))
### Features
* **YouTube Music:** Support version `8.05.51` ([128441e](https://github.com/ReVanced/revanced-patches/commit/128441e78bc0d096c3fc2f57782ab90c39c3ae4b))
# [5.12.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.5...v5.12.0-dev.6) (2025-02-16)
### Bug Fixes
* Allow changing default settings for existing app installs ([#4464](https://github.com/ReVanced/revanced-patches/issues/4464)) ([1bd7986](https://github.com/ReVanced/revanced-patches/commit/1bd7986823e774a929c8a9102a7cc96e245d5274))
# [5.12.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.4...v5.12.0-dev.5) (2025-02-13)
### Bug Fixes
* **YouTube:** Remove obsolete 18.x targets ([#4454](https://github.com/ReVanced/revanced-patches/issues/4454)) ([a006758](https://github.com/ReVanced/revanced-patches/commit/a0067581d0f877e1b4eb1f888a25786f09676b2e))
# [5.12.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.3...v5.12.0-dev.4) (2025-02-11)
### Features
* **YouTube Music:** Support version `8.05.50` ([#4439](https://github.com/ReVanced/revanced-patches/issues/4439)) ([b31fed9](https://github.com/ReVanced/revanced-patches/commit/b31fed98901fcda1bce6f05eb0de63280c689fa0))
# [5.12.0-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.2...v5.12.0-dev.3) (2025-02-11)
### Bug Fixes
* **Windy.app:** Remove obsolete `Unlock pro` patch ([#4428](https://github.com/ReVanced/revanced-patches/issues/4428)) ([83d116e](https://github.com/ReVanced/revanced-patches/commit/83d116e8fd3935ee431cfdf0b8e095d04ee77259))
# [5.12.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.12.0-dev.1...v5.12.0-dev.2) (2025-02-11)
### Features
* **Return YouTube Dislike:** add `Show estimated likes` setting ([#4443](https://github.com/ReVanced/revanced-patches/issues/4443)) ([9a88b42](https://github.com/ReVanced/revanced-patches/commit/9a88b4239fd63d5f91105fec8e7d59d318a5d09a))
# [5.12.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.11.0...v5.12.0-dev.1) (2025-02-10)
### Features
* **YouTube - SponsorBlock:** Redesign skip buttons ([#4427](https://github.com/ReVanced/revanced-patches/issues/4427)) ([8f4883f](https://github.com/ReVanced/revanced-patches/commit/8f4883fc002420bfb4056401e23445c99e1d3fce))
# [5.11.0](https://github.com/ReVanced/revanced-patches/compare/v5.10.0...v5.11.0) (2025-02-07)
### Bug Fixes
* Fix broken `Remove screen capture restriction`, `Remove screenshot restriction`, `Spoof Wi-Fi connection`, and `Export internal data documents provider` patch ([#4405](https://github.com/ReVanced/revanced-patches/issues/4405)) ([1d52b74](https://github.com/ReVanced/revanced-patches/commit/1d52b7478d34e699d8c629eeaa9fdbb470b7d5c8))
* **YouTube - Enable slide to seek:** Change patch to default include ([50358cd](https://github.com/ReVanced/revanced-patches/commit/50358cddea3eef4051d248040d23f774521dce00))
* **YouTube - Hide layout components:** Hide new type of community post ([#4404](https://github.com/ReVanced/revanced-patches/issues/4404)) ([f67ab2b](https://github.com/ReVanced/revanced-patches/commit/f67ab2baf25d543ceb55fcec48bda441ebf2b998))
* **YouTube - Theme:** Use custom seekbar color for cairo startup animation ([#4399](https://github.com/ReVanced/revanced-patches/issues/4399)) ([1cba294](https://github.com/ReVanced/revanced-patches/commit/1cba2948a6787118eb380ffcec35ee4fb99447ea))
### Features
* **YouTube - Change start page:** Add additional start pages ([#4413](https://github.com/ReVanced/revanced-patches/issues/4413)) ([b434182](https://github.com/ReVanced/revanced-patches/commit/b434182df69313c4eb5f0dfd98101cb80e46ead2))
# [5.11.0-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.11.0-dev.1...v5.11.0-dev.2) (2025-02-06)
### Bug Fixes
* Fix broken `Remove screen capture restriction`, `Remove screenshot restriction`, `Spoof Wi-Fi connection`, and `Export internal data documents provider` patch ([#4405](https://github.com/ReVanced/revanced-patches/issues/4405)) ([1d52b74](https://github.com/ReVanced/revanced-patches/commit/1d52b7478d34e699d8c629eeaa9fdbb470b7d5c8))
# [5.11.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.3...v5.11.0-dev.1) (2025-02-05)
### Features
* **YouTube - Change start page:** Add additional start pages ([#4413](https://github.com/ReVanced/revanced-patches/issues/4413)) ([b434182](https://github.com/ReVanced/revanced-patches/commit/b434182df69313c4eb5f0dfd98101cb80e46ead2))
## [5.10.1-dev.3](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.2...v5.10.1-dev.3) (2025-02-03)
### Bug Fixes
* **YouTube - Hide layout components:** Hide new type of community post ([#4404](https://github.com/ReVanced/revanced-patches/issues/4404)) ([f67ab2b](https://github.com/ReVanced/revanced-patches/commit/f67ab2baf25d543ceb55fcec48bda441ebf2b998))
## [5.10.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v5.10.1-dev.1...v5.10.1-dev.2) (2025-02-03)
### Bug Fixes
* **YouTube - Enable slide to seek:** Change patch to default include ([50358cd](https://github.com/ReVanced/revanced-patches/commit/50358cddea3eef4051d248040d23f774521dce00))
## [5.10.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.10.0...v5.10.1-dev.1) (2025-02-02)
### Bug Fixes
* **YouTube - Theme:** Use custom seekbar color for cairo startup animation ([#4399](https://github.com/ReVanced/revanced-patches/issues/4399)) ([1cba294](https://github.com/ReVanced/revanced-patches/commit/1cba2948a6787118eb380ffcec35ee4fb99447ea))
# [5.10.0](https://github.com/ReVanced/revanced-patches/compare/v5.9.0...v5.10.0) (2025-01-31)
### Bug Fixes
* **SwissId - Play integrity Removal:** Add recommended app version ([#4370](https://github.com/ReVanced/revanced-patches/issues/4370)) ([d8ed474](https://github.com/ReVanced/revanced-patches/commit/d8ed474b165f094fdedc32caaae1f82ebc99eb3d))
* Use correct path to fix invalid file paths ([5ff4ee8](https://github.com/ReVanced/revanced-patches/commit/5ff4ee823da55c7b135eab8b62e07be465612b55))
* **YouTube - Hide ads:** fix 'Hide the Visit store button on channel pages' not working ([#4364](https://github.com/ReVanced/revanced-patches/issues/4364)) ([9d63ea9](https://github.com/ReVanced/revanced-patches/commit/9d63ea9a10ab5128ce18a1f53a946e84550da258))
* **YouTube - Hide Ads:** Hide end screen store banner without leaving empty space ([#4367](https://github.com/ReVanced/revanced-patches/issues/4367)) ([7e68390](https://github.com/ReVanced/revanced-patches/commit/7e683906418434dd4e2104337d73a2292415c615))
* **YouTube - Hide ads:** Hide new types of tablet ads ([574bcc8](https://github.com/ReVanced/revanced-patches/commit/574bcc844746b7445ec3e93b47daceafefad85e7))
* **YouTube - Hide layout components:** Hide new kind of community post ([#4341](https://github.com/ReVanced/revanced-patches/issues/4341)) ([02685c4](https://github.com/ReVanced/revanced-patches/commit/02685c4567aca55f22d45dc238a7d1f0ea264143))
* **YouTube - Hide seekbar:** Do not hide player seekbar if hide feed seekbar is enabled ([#4333](https://github.com/ReVanced/revanced-patches/issues/4333)) ([f5cf6f2](https://github.com/ReVanced/revanced-patches/commit/f5cf6f2a445492d33815a9772f49deac2d70eba9))
* **YouTube - Hide video description components:** Use correct string key names ([0f28c2b](https://github.com/ReVanced/revanced-patches/commit/0f28c2b44c0051ea7ab3136433b84c73321cf5bd))
* **YouTube - Spoof video streams:** Update settings side effects summary text ([#4369](https://github.com/ReVanced/revanced-patches/issues/4369)) ([e5b3aa1](https://github.com/ReVanced/revanced-patches/commit/e5b3aa1cc6a2465cb006487d528de888bc7cd430))
* **YouTube - Theme:** Fix 19.25 - 19.45 patch error ([5b47a5f](https://github.com/ReVanced/revanced-patches/commit/5b47a5f0f6299daaae209341064fd85f16ca18a6))
* **YouTube - Theme:** Replace custom seekbar gradient colors instead of disabling ([#4329](https://github.com/ReVanced/revanced-patches/issues/4329)) ([f03da98](https://github.com/ReVanced/revanced-patches/commit/f03da983051021e0c372557a5354d5d967409564))
### Features
* **YouTube - Hide ads:** Add `Hide end screen store banner` ([#4351](https://github.com/ReVanced/revanced-patches/issues/4351)) ([5505087](https://github.com/ReVanced/revanced-patches/commit/55050878028fed82b0f583a9f7ba06b8f267f8ec))
* **YouTube - Hide video description components:** Add `Hide How this content was made section` ([#4355](https://github.com/ReVanced/revanced-patches/issues/4355)) ([68ec54e](https://github.com/ReVanced/revanced-patches/commit/68ec54ef850ae8d6461dd0ef2846e6efbb59e482))
* **YouTube - Theme:** Add option to use custom seekbar accent color ([#4337](https://github.com/ReVanced/revanced-patches/issues/4337)) ([952b4fc](https://github.com/ReVanced/revanced-patches/commit/952b4fc4c9291e1a3e71437b503857763c973dd4))
* **YouTube:** Add patch `Disable HDR video` ([#4347](https://github.com/ReVanced/revanced-patches/issues/4347)) ([0528f7c](https://github.com/ReVanced/revanced-patches/commit/0528f7cad856a2b1347e41944167b0583fc4a3d9))
# [5.10.0-dev.11](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.10...v5.10.0-dev.11) (2025-01-30)
### Bug Fixes
* Use correct path to fix invalid file paths ([5ff4ee8](https://github.com/ReVanced/revanced-patches/commit/5ff4ee823da55c7b135eab8b62e07be465612b55))
# [5.10.0-dev.10](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.9...v5.10.0-dev.10) (2025-01-29)
### Bug Fixes
* **YouTube - Hide ads:** Hide new types of tablet ads ([574bcc8](https://github.com/ReVanced/revanced-patches/commit/574bcc844746b7445ec3e93b47daceafefad85e7))
# [5.10.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.8...v5.10.0-dev.9) (2025-01-29)
### Bug Fixes
* **SwissId - Play integrity Removal:** Add recommended app version ([#4370](https://github.com/ReVanced/revanced-patches/issues/4370)) ([d8ed474](https://github.com/ReVanced/revanced-patches/commit/d8ed474b165f094fdedc32caaae1f82ebc99eb3d))
# [5.10.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.7...v5.10.0-dev.8) (2025-01-29)
### Bug Fixes
* **YouTube - Spoof video streams:** Update settings side effects summary text ([#4369](https://github.com/ReVanced/revanced-patches/issues/4369)) ([e5b3aa1](https://github.com/ReVanced/revanced-patches/commit/e5b3aa1cc6a2465cb006487d528de888bc7cd430))
# [5.10.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.6...v5.10.0-dev.7) (2025-01-29)
### Bug Fixes
* **YouTube - Hide ads:** fix 'Hide the Visit store button on channel pages' not working ([#4364](https://github.com/ReVanced/revanced-patches/issues/4364)) ([9d63ea9](https://github.com/ReVanced/revanced-patches/commit/9d63ea9a10ab5128ce18a1f53a946e84550da258))
# [5.10.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.5...v5.10.0-dev.6) (2025-01-29)
### Bug Fixes
* **YouTube - Hide Ads:** Hide end screen store banner without leaving empty space ([#4367](https://github.com/ReVanced/revanced-patches/issues/4367)) ([7e68390](https://github.com/ReVanced/revanced-patches/commit/7e683906418434dd4e2104337d73a2292415c615))
# [5.10.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.4...v5.10.0-dev.5) (2025-01-27)
### Bug Fixes
* **YouTube - Hide video description components:** Use correct string key names ([0f28c2b](https://github.com/ReVanced/revanced-patches/commit/0f28c2b44c0051ea7ab3136433b84c73321cf5bd))
# [5.10.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.10.0-dev.3...v5.10.0-dev.4) (2025-01-27)

View File

@@ -1,4 +1,11 @@
android.namespace = "app.revanced.extension"
android {
namespace = "app.revanced.extension"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
compileOnly(libs.annotation)

View File

@@ -12,7 +12,7 @@ import android.os.Handler;
import androidx.annotation.RequiresApi;
/** @noinspection deprecation, unused */
@SuppressWarnings({"deprecation", "unused"})
public class SpoofWifiPatch {
// Used to check what the (real or fake) active network is (take a look at `hasTransport`).

View File

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

View File

@@ -1,5 +1,6 @@
package app.revanced.extension.all.misc.directory.documentsprovider;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ProviderInfo;
@@ -23,6 +24,7 @@ import java.util.Objects;
/**
* A DocumentsProvider that allows access to the app's internal data directory.
*/
@SuppressLint("LongLogTag")
public class InternalDataDocumentsProvider extends DocumentsProvider {
private static final String[] rootColumns =
{"root_id", "mime_types", "flags", "icon", "title", "summary", "document_id"};

View File

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

View File

@@ -5,7 +5,8 @@ import android.os.Build;
import androidx.annotation.RequiresApi;
public final class RemoveScreencaptureRestrictionPatch {
@SuppressWarnings("unused")
public final class RemoveScreenCaptureRestrictionPatch {
// Member of AudioAttributes.Builder
@RequiresApi(api = Build.VERSION_CODES.Q)
public static AudioAttributes.Builder setAllowedCapturePolicy(final AudioAttributes.Builder builder, final int capturePolicy) {

View File

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

View File

@@ -3,6 +3,7 @@ package app.revanced.extension.all.screenshot.removerestriction;
import android.view.Window;
import android.view.WindowManager;
@SuppressWarnings("unused")
public class RemoveScreenshotRestrictionPatch {
public static void addFlags(Window window, int flags) {

View File

@@ -4,7 +4,7 @@ plugins {
android {
namespace = "app.revanced.extension"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 24

View File

@@ -1 +1,5 @@
// Do not remove. Necessary for the extension plugin to be applied to the project.
android {
defaultConfig {
minSdk = 26
}
}

View File

@@ -0,0 +1,4 @@
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:nunl:stub"))
}

View File

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

View File

@@ -0,0 +1,114 @@
package app.revanced.extension.nunl.ads;
import nl.nu.performance.api.client.interfaces.Block;
import nl.nu.performance.api.client.unions.SmallArticleLinkFlavor;
import nl.nu.performance.api.client.objects.*;
import java.util.ArrayList;
import java.util.List;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class HideAdsPatch {
private static final String[] blockedHeaderBlocks = {
"Aanbiedingen (Adverteerders)",
"Aangeboden door NUshop"
};
// "Rubrieken" menu links to ads.
private static final String[] blockedLinkBlocks = {
"Van onze adverteerders"
};
public static void filterAds(List<Block> blocks) {
try {
ArrayList<Block> cleanedList = new ArrayList<>();
boolean skipFullHeader = false;
boolean skipUntilDivider = false;
int index = 0;
while (index < blocks.size()) {
Block currentBlock = blocks.get(index);
// Because of pagination, we might not see the Divider in front of it.
// Just remove it as is and leave potential extra spacing visible on the screen.
if (currentBlock instanceof DpgBannerBlock) {
index++;
continue;
}
if (index + 1 < blocks.size()) {
// Filter Divider -> DpgMediaBanner -> Divider.
if (currentBlock instanceof DividerBlock
&& blocks.get(index + 1) instanceof DpgBannerBlock) {
index += 2;
continue;
}
// Filter Divider -> LinkBlock (... -> LinkBlock -> LinkBlock-> LinkBlock -> Divider).
if (currentBlock instanceof DividerBlock
&& blocks.get(index + 1) instanceof LinkBlock linkBlock) {
Link link = linkBlock.getLink();
if (link != null && link.getTitle() != null) {
for (String blockedLinkBlock : blockedLinkBlocks) {
if (blockedLinkBlock.equals(link.getTitle().getText())) {
skipUntilDivider = true;
break;
}
}
if (skipUntilDivider) {
index++;
continue;
}
}
}
}
// Skip LinkBlocks with a "flavor" claiming to be "isPartner" (sponsored inline ads).
if (currentBlock instanceof LinkBlock linkBlock
&& linkBlock.getLink() != null
&& linkBlock.getLink().getLinkFlavor() instanceof SmallArticleLinkFlavor smallArticleLinkFlavor
&& smallArticleLinkFlavor.isPartner() != null
&& smallArticleLinkFlavor.isPartner()) {
index++;
continue;
}
if (currentBlock instanceof DividerBlock) {
skipUntilDivider = false;
}
// Filter HeaderBlock with known ads until next HeaderBlock.
if (currentBlock instanceof HeaderBlock headerBlock) {
StyledText headerText = headerBlock.component20();
if (headerText != null) {
skipFullHeader = false;
for (String blockedHeaderBlock : blockedHeaderBlocks) {
if (blockedHeaderBlock.equals(headerText.getText())) {
skipFullHeader = true;
break;
}
}
if (skipFullHeader) {
index++;
continue;
}
}
}
if (!skipFullHeader && !skipUntilDivider) {
cleanedList.add(currentBlock);
}
index++;
}
// Replace list in-place to not deal with moving the result to the correct register in smali.
blocks.clear();
blocks.addAll(cleanedList);
} catch (Exception ex) {
Logger.printException(() -> "filterAds failure", ex);
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
package nl.nu.performance.api.client.interfaces;
public class Block {
}

View File

@@ -0,0 +1,7 @@
package nl.nu.performance.api.client.objects;
import nl.nu.performance.api.client.interfaces.Block;
public class DividerBlock extends Block {
}

View File

@@ -0,0 +1,7 @@
package nl.nu.performance.api.client.objects;
import nl.nu.performance.api.client.interfaces.Block;
public class DpgBannerBlock extends Block {
}

View File

@@ -0,0 +1,10 @@
package nl.nu.performance.api.client.objects;
import nl.nu.performance.api.client.interfaces.Block;
public class HeaderBlock extends Block {
// returns title
public final StyledText component20() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -0,0 +1,13 @@
package nl.nu.performance.api.client.objects;
import nl.nu.performance.api.client.unions.LinkFlavor;
public class Link {
public final StyledText getTitle() {
throw new UnsupportedOperationException("Stub");
}
public final LinkFlavor getLinkFlavor() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -0,0 +1,10 @@
package nl.nu.performance.api.client.objects;
import android.os.Parcelable;
import nl.nu.performance.api.client.interfaces.Block;
public abstract class LinkBlock extends Block implements Parcelable {
public final Link getLink() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -0,0 +1,7 @@
package nl.nu.performance.api.client.objects;
public class StyledText {
public final String getText() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -0,0 +1,4 @@
package nl.nu.performance.api.client.unions;
public interface LinkFlavor {
}

View File

@@ -0,0 +1,7 @@
package nl.nu.performance.api.client.unions;
public class SmallArticleLinkFlavor implements LinkFlavor {
public final Boolean isPartner() {
throw new UnsupportedOperationException("Stub");
}
}

View File

@@ -4,7 +4,7 @@ plugins {
android {
namespace = "app.revanced.extension"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 24

View File

@@ -356,33 +356,24 @@ public class Utils {
public static Context getContext() {
if (context == null) {
Logger.initializationException(Utils.class, "Context is null, returning null!", null);
Logger.initializationException(Utils.class, "Context is not set by extension hook, returning null", null);
}
return context;
}
public static void setContext(Context appContext) {
// Must initially set context as the language settings needs it.
// Must initially set context to check the app language.
context = appContext;
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
AppLanguage language = BaseSettings.REVANCED_LANGUAGE.get();
if (language != AppLanguage.DEFAULT) {
// Create a new context with the desired language.
Logger.printDebug(() -> "Using app language: " + language);
Configuration config = appContext.getResources().getConfiguration();
config.setLocale(language.getLocale());
context = appContext.createConfigurationContext(config);
}
// In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies.
// Calling the regular printDebug method here can cause a Settings context null pointer exception,
// even though the context is already set before the call.
//
// The initialization logger methods do not directly or indirectly
// reference the Context or any Settings and are unaffected by this problem.
//
// Info level also helps debug if a patch hook is called before
// the context is set since debug logging is off by default.
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
}
public static void setClipboard(@NonNull String text) {

View File

@@ -29,6 +29,6 @@ public class BaseSettings {
public static final BooleanSetting SPOOF_VIDEO_STREAMS_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_video_streams_ios_force_avc", FALSE, true,
"revanced_spoof_video_streams_ios_force_avc_user_dialog_message", new SpoofiOSAvailability());
// Client type must be last spoof setting due to cyclic references.
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR, true, parent(SPOOF_VIDEO_STREAMS));
public static final EnumSetting<ClientType> SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_UNPLUGGED, true, parent(SPOOF_VIDEO_STREAMS));
}

View File

@@ -47,6 +47,10 @@ public class BooleanSetting extends Setting<Boolean> {
*/
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
setting.value = Objects.requireNonNull(newValue);
if (setting.isSetToDefault()) {
setting.removeFromPreferences();
}
}
@Override
@@ -65,10 +69,8 @@ public class BooleanSetting extends Setting<Boolean> {
}
@Override
public void save(@NonNull Boolean newValue) {
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
value = Objects.requireNonNull(newValue);
preferences.saveBoolean(key, newValue);
public void saveToPreferences() {
preferences.saveBoolean(key, value);
}
@NonNull

View File

@@ -89,10 +89,8 @@ public class EnumSetting<T extends Enum<?>> extends Setting<T> {
}
@Override
public void save(@NonNull T newValue) {
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
value = Objects.requireNonNull(newValue);
preferences.saveEnumAsString(key, newValue);
public void saveToPreferences() {
preferences.saveEnumAsString(key, value);
}
@NonNull

View File

@@ -55,10 +55,8 @@ public class FloatSetting extends Setting<Float> {
}
@Override
public void save(@NonNull Float newValue) {
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
value = Objects.requireNonNull(newValue);
preferences.saveFloatString(key, newValue);
public void saveToPreferences() {
preferences.saveFloatString(key, value);
}
@NonNull

View File

@@ -55,10 +55,8 @@ public class IntegerSetting extends Setting<Integer> {
}
@Override
public void save(@NonNull Integer newValue) {
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
value = Objects.requireNonNull(newValue);
preferences.saveIntegerString(key, newValue);
public void saveToPreferences() {
preferences.saveIntegerString(key, value);
}
@NonNull

View File

@@ -55,10 +55,8 @@ public class LongSetting extends Setting<Long> {
}
@Override
public void save(@NonNull Long newValue) {
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
value = Objects.requireNonNull(newValue);
preferences.saveLongString(key, newValue);
public void saveToPreferences() {
preferences.saveLongString(key, value);
}
@NonNull

View File

@@ -14,7 +14,6 @@ import java.util.*;
import static app.revanced.extension.shared.StringRef.str;
@SuppressWarnings("unused")
public abstract class Setting<T> {
/**
@@ -288,6 +287,13 @@ public abstract class Setting<T> {
*/
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
setting.setValueFromString(newValue);
// Clear the preference value since default is used, to allow changing
// the changing the default for a future release. Without this after upgrading
// the saved value will be whatever was the default when the app was first installed.
if (setting.isSetToDefault()) {
setting.removeFromPreferences();
}
}
/**
@@ -303,7 +309,33 @@ public abstract class Setting<T> {
/**
* Persistently saves the value.
*/
public abstract void save(@NonNull T newValue);
public final void save(@NonNull T newValue) {
if (value.equals(newValue)) {
return;
}
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
value = Objects.requireNonNull(newValue);
if (defaultValue.equals(newValue)) {
removeFromPreferences();
} else {
saveToPreferences();
}
}
/**
* Save {@link #value} to {@link #preferences}.
*/
protected abstract void saveToPreferences();
/**
* Remove {@link #value} from {@link #preferences}.
*/
protected final void removeFromPreferences() {
Logger.printDebug(() -> "Clearing stored preference value (reset to default): " + key);
preferences.removeKey(key);
}
@NonNull
public abstract T get();

View File

@@ -55,10 +55,8 @@ public class StringSetting extends Setting<String> {
}
@Override
public void save(@NonNull String newValue) {
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
value = Objects.requireNonNull(newValue);
preferences.saveString(key, newValue);
public void saveToPreferences() {
preferences.saveString(key, value);
}
@NonNull

View File

@@ -158,16 +158,16 @@ public abstract class AbstractPreferenceFragment extends PreferenceFragment {
/**
* Syncs all UI Preferences to any {@link Setting} they represent.
*/
private void updatePreferenceScreen(@NonNull PreferenceScreen screen,
private void updatePreferenceScreen(@NonNull PreferenceGroup group,
boolean syncSettingValue,
boolean applySettingToPreference) {
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
// but there are many more Settings than UI preferences so it's more efficient to only check
// the Preferences.
for (int i = 0, prefCount = screen.getPreferenceCount(); i < prefCount; i++) {
Preference pref = screen.getPreference(i);
if (pref instanceof PreferenceScreen) {
updatePreferenceScreen((PreferenceScreen) pref, syncSettingValue, applySettingToPreference);
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
Preference pref = group.getPreference(i);
if (pref instanceof PreferenceGroup subGroup) {
updatePreferenceScreen(subGroup, syncSettingValue, applySettingToPreference);
} else if (pref.hasKey()) {
String key = pref.getKey();
Setting<?> setting = Setting.getSettingFromPath(key);

View File

@@ -0,0 +1,34 @@
package app.revanced.extension.shared.settings.preference;
import android.annotation.SuppressLint;
import android.content.Context;
import android.preference.PreferenceCategory;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
/**
* Empty preference category with no title, used to organize and group related preferences together.
*/
@SuppressWarnings({"unused", "deprecation"})
public class NoTitlePreferenceCategory extends PreferenceCategory {
public NoTitlePreferenceCategory(Context context, AttributeSet attrs) {
super(context, attrs);
}
public NoTitlePreferenceCategory(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public NoTitlePreferenceCategory(Context context) {
super(context);
}
@Override
@SuppressLint("MissingSuperCall")
protected View onCreateView(ViewGroup parent) {
// Return an empty, zero-height view to eliminate spacing
return new View(getContext());
}
}

View File

@@ -66,22 +66,6 @@ public enum ClientType {
true,
"Android Creator"
),
ANDROID_VR(
ANDROID_VR_NO_AUTH.id,
ANDROID_VR_NO_AUTH.clientName,
ANDROID_VR_NO_AUTH.packageName,
ANDROID_VR_NO_AUTH.deviceMake,
ANDROID_VR_NO_AUTH.deviceModel,
ANDROID_VR_NO_AUTH.osName,
ANDROID_VR_NO_AUTH.osVersion,
ANDROID_VR_NO_AUTH.androidSdkVersion,
ANDROID_VR_NO_AUTH.buildId,
ANDROID_VR_NO_AUTH.cronetVersion,
ANDROID_VR_NO_AUTH.clientVersion,
ANDROID_VR_NO_AUTH.requiresAuth,
true,
"Android VR"
),
IOS_UNPLUGGED(
33,
"IOS_UNPLUGGED",
@@ -112,6 +96,22 @@ public enum ClientType {
forceAVC()
? "iOS TV Force AVC"
: "iOS TV"
),
ANDROID_VR_AUTH(
ANDROID_VR_NO_AUTH.id,
ANDROID_VR_NO_AUTH.clientName,
ANDROID_VR_NO_AUTH.packageName,
ANDROID_VR_NO_AUTH.deviceMake,
ANDROID_VR_NO_AUTH.deviceModel,
ANDROID_VR_NO_AUTH.osName,
ANDROID_VR_NO_AUTH.osVersion,
ANDROID_VR_NO_AUTH.androidSdkVersion,
ANDROID_VR_NO_AUTH.buildId,
ANDROID_VR_NO_AUTH.cronetVersion,
ANDROID_VR_NO_AUTH.clientVersion,
ANDROID_VR_NO_AUTH.requiresAuth,
true,
"Android VR Auth"
);
private static boolean forceAVC() {

View File

@@ -107,6 +107,21 @@ public class SpoofVideoStreamsPatch {
return false;
}
/**
* Injection point.
* Turns off a feature flag that interferes with spoofing.
*/
public static boolean useMediaFetchHotConfigReplacement(boolean original) {
if (original) {
Logger.printDebug(() -> "useMediaFetchHotConfigReplacement is set on");
}
if (!SPOOF_STREAMING_DATA) {
return original;
}
return false;
}
/**
* Injection point.
*/

View File

@@ -4,7 +4,7 @@ plugins {
android {
namespace = "app.revanced.extension"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 24

View File

@@ -3,3 +3,14 @@ dependencies {
compileOnly(project(":extensions:tiktok:stub"))
compileOnly(libs.annotation)
}
android {
defaultConfig {
minSdk = 22
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

@@ -21,9 +21,11 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
protected void syncSettingWithPreference(@NonNull Preference pref,
@NonNull Setting<?> setting,
boolean applySettingToPreference) {
if (pref instanceof RangeValuePreference rangeValuePref) {
if (pref instanceof RangeValuePreference) {
RangeValuePreference rangeValuePref = (RangeValuePreference) pref;
Setting.privateSetValueFromString(setting, rangeValuePref.getValue());
} else if (pref instanceof DownloadPathPreference downloadPathPref) {
} else if (pref instanceof DownloadPathPreference) {
DownloadPathPreference downloadPathPref = (DownloadPathPreference) pref;
Setting.privateSetValueFromString(setting, downloadPathPref.getValue());
} else {
super.syncSettingWithPreference(pref, setting, applySettingToPreference);
@@ -32,7 +34,7 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
@Override
protected void initialize() {
final var context = getContext();
final var context = getActivity();
// Currently no resources can be compiled for TikTok (fails with aapt error).
// So all TikTok Strings are hard coded in the extension.

View File

@@ -1,37 +1,60 @@
package app.revanced.extension.tiktok.spoof.sim;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.tiktok.settings.Settings;
@SuppressWarnings("unused")
public class SpoofSimPatch {
private static final boolean ENABLED = Settings.SIM_SPOOF.get();
/**
* During app startup native code can be called with no obvious way to set the context.
* Cannot check if sim spoofing is enabled or the app will crash since no context is set.
*/
private static boolean isContextNotSet(String fieldSpoofed) {
if (Utils.getContext() != null) {
return false;
}
Logger.initializationException(SpoofSimPatch.class,
"Context is not yet set, cannot spoof: " + fieldSpoofed, null);
return true;
}
public static String getCountryIso(String value) {
if (ENABLED) {
if (isContextNotSet("countryIso")) return value;
if (Settings.SIM_SPOOF.get()) {
String iso = Settings.SIM_SPOOF_ISO.get();
Logger.printDebug(() -> "Spoofing sim ISO from: " + value + " to: " + iso);
Logger.printDebug(() -> "Spoofing countryIso from: " + value + " to: " + iso);
return iso;
}
return value;
}
public static String getOperator(String value) {
if (ENABLED) {
if (isContextNotSet("MCC-MNC")) return value;
if (Settings.SIM_SPOOF.get()) {
String mcc_mnc = Settings.SIMSPOOF_MCCMNC.get();
Logger.printDebug(() -> "Spoofing sim MCC-MNC from: " + value + " to: " + mcc_mnc);
return mcc_mnc;
}
return value;
}
public static String getOperatorName(String value) {
if (ENABLED) {
if (isContextNotSet("operatorName")) return value;
if (Settings.SIM_SPOOF.get()) {
String operator = Settings.SIMSPOOF_OP_NAME.get();
Logger.printDebug(() -> "Spoofing sim operator from: " + value + " to: " + operator);
Logger.printDebug(() -> "Spoofing sim operatorName from: " + value + " to: " + operator);
return operator;
}
return value;
}
}

View File

@@ -4,14 +4,9 @@ plugins {
android {
namespace = "app.revanced.extension"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
minSdk = 22
}
}

View File

@@ -1,3 +1,9 @@
dependencies {
compileOnly(project(":extensions:tumblr:stub"))
}
android {
defaultConfig {
minSdk = 26
}
}

View File

@@ -1,17 +1,14 @@
android.namespace = "app.revanced.extension"
plugins {
id(libs.plugins.android.library.get().pluginId)
}
android {
namespace = "app.revanced.extension"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
minSdk = 26
}
}

View File

@@ -6,3 +6,14 @@ dependencies {
compileOnly(libs.annotation)
compileOnly(libs.appcompat)
}
android {
defaultConfig {
minSdk = 21
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}

View File

@@ -4,14 +4,9 @@ plugins {
android {
namespace = "app.revanced.extension"
compileSdk = 33
compileSdk = 34
defaultConfig {
minSdk = 24
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
minSdk = 21
}
}

View File

@@ -1,8 +1,11 @@
//noinspection GradleDependency
android.compileSdk = 33
dependencies {
compileOnly(project(":extensions:shared:library"))
compileOnly(project(":extensions:youtube:stub"))
compileOnly(libs.annotation)
}
android {
defaultConfig {
minSdk = 26
}
}

View File

@@ -1,7 +1,7 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused")
public class BackgroundPlaybackPatch {
@@ -23,16 +23,7 @@ public class BackgroundPlaybackPatch {
// 7. Close the Short
// 8. Resume playing the regular video
// 9. Minimize the app (PIP should appear)
if (!VideoInformation.lastVideoIdIsShort()) {
return true; // Definitely is not a Short.
}
// TODO: Add better hook.
// Might be a Shorts, or might be a prior regular video on screen again after a Shorts was closed.
// This incorrectly prevents PIP if player is in WATCH_WHILE_MINIMIZED after closing a Shorts,
// But there's no way around this unless an additional hook is added to definitively detect
// the Shorts player is on screen. This use case is unusual anyways so it's not a huge concern.
return !PlayerType.getCurrent().isNoneHiddenOrMinimized();
return !ShortsPlayerState.isOpen();
}
/**

View File

@@ -1,9 +1,17 @@
package app.revanced.extension.youtube.patches;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import android.view.View;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Utils;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public class ChangeFormFactorPatch {
@@ -41,14 +49,57 @@ public class ChangeFormFactorPatch {
@Nullable
private static final Integer FORM_FACTOR_TYPE = Settings.CHANGE_FORM_FACTOR.get().formFactorType;
private static final boolean USING_AUTOMOTIVE_TYPE = Objects.requireNonNull(
FormFactor.AUTOMOTIVE.formFactorType).equals(FORM_FACTOR_TYPE);
/**
* Injection point.
*/
public static int getFormFactor(int original) {
return FORM_FACTOR_TYPE == null
? original
: FORM_FACTOR_TYPE;
if (FORM_FACTOR_TYPE == null) return original;
if (USING_AUTOMOTIVE_TYPE) {
// Do not change if the player is opening or is opened,
// otherwise the video description cannot be opened.
PlayerType current = PlayerType.getCurrent();
if (current.isMaximizedOrFullscreen() || current == PlayerType.WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED) {
Logger.printDebug(() -> "Using original form factor for player");
return original;
}
if (!NavigationBar.isSearchBarActive()) {
// Automotive type shows error 400 when opening a channel page and using some explore tab.
// This is a bug in unpatched YouTube that occurs on actual Android Automotive devices.
// Work around the issue by using the original form factor if not in search and the
// navigation back button is present.
if (NavigationBar.isBackButtonVisible()) {
Logger.printDebug(() -> "Using original form factor, as back button is visible without search present");
return original;
}
// Do not change library tab otherwise watch history is hidden.
// Do this check last since the current navigation button is required.
if (NavigationButton.getSelectedNavigationButton() == NavigationButton.LIBRARY) {
return original;
}
}
}
return FORM_FACTOR_TYPE;
}
/**
* Injection point.
*/
public static void navigationTabCreated(NavigationButton button, View tabView) {
// On first startup of the app the navigation buttons are fetched and updated.
// If the user immediately opens the 'You' or opens a video, then the call to
// update the navigtation buttons will use the non automotive form factor
// and the explore tab is missing.
// Fixing this is not so simple because of the concurrent calls for the player and You tab.
// For now, always hide the explore tab.
if (USING_AUTOMOTIVE_TYPE && button == NavigationButton.EXPLORE) {
tabView.setVisibility(View.GONE);
}
}
}

View File

@@ -23,21 +23,30 @@ public final class ChangeStartPagePatch {
/**
* Browse id.
*/
ALL_SUBSCRIPTIONS("FEchannels", TRUE),
BROWSE("FEguide_builder", TRUE),
EXPLORE("FEexplore", TRUE),
HISTORY("FEhistory", TRUE),
LIBRARY("FElibrary", TRUE),
MOVIE("FEstorefront", TRUE),
NOTIFICATIONS("FEactivity", TRUE),
PLAYLISTS("FEplaylist_aggregation", TRUE),
SUBSCRIPTIONS("FEsubscriptions", TRUE),
TRENDING("FEtrending", TRUE),
YOUR_CLIPS("FEclips", TRUE),
/**
* Channel id, this can be used as a browseId.
*/
COURSES("UCtFRv9O2AHqOZjjynzrv-xg", TRUE),
FASHION("UCrpQ4p1Ql_hG8rKXIKM1MOQ", TRUE),
GAMING("UCOpNcN46UbXVtpKMrmU4Abg", TRUE),
LIVE("UC4R8DWoMoI7CAwX8_LjQHig", TRUE),
MUSIC("UC-9-kyTW8ZkZNDHQJ6FgpwQ", TRUE),
NEWS("UCYfdidRxbB8Qhf0Nx7ioOYw", TRUE),
SHOPPING("UCkYQyvc_i9hXEo4xic9Hh2g", TRUE),
SPORTS("UCEgdi0XIXXZ-qJOFPf4JSKw", TRUE),
VIRTUAL_REALITY("UCzuqhhs6NWbgTzMuM09WKDQ", TRUE),
/**
* Playlist id, this can be used as a browseId.
@@ -51,12 +60,12 @@ public final class ChangeStartPagePatch {
SEARCH("com.google.android.youtube.action.open.search", FALSE),
SHORTS("com.google.android.youtube.action.open.shorts", FALSE);
@Nullable
final Boolean isBrowseId;
@NonNull
final String id;
@Nullable
final Boolean isBrowseId;
StartPage(@NonNull String id, @Nullable Boolean isBrowseId) {
this.id = id;
this.isBrowseId = isBrowseId;
@@ -122,7 +131,7 @@ public final class ChangeStartPagePatch {
}
appLaunched = true;
final String intentAction = START_PAGE.id;
String intentAction = START_PAGE.id;
Logger.printDebug(() -> "Changing intent action to " + intentAction);
intent.setAction(intentAction);
}

View File

@@ -1,7 +1,7 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused")
public class DisableAutoCaptionsPatch {
@@ -14,7 +14,7 @@ public class DisableAutoCaptionsPatch {
public static boolean autoCaptionsEnabled() {
return Settings.AUTO_CAPTIONS.get()
// Do not use auto captions for Shorts.
&& !PlayerType.getCurrent().isNoneHiddenOrSlidingMinimized();
&& ShortsPlayerState.isOpen();
}
}

View File

@@ -1,24 +0,0 @@
package app.revanced.extension.youtube.patches;
import android.annotation.SuppressLint;
import android.widget.ImageView;
import app.revanced.extension.youtube.settings.Settings;
/** @noinspection unused*/
public final class DisableSuggestedVideoEndScreenPatch {
@SuppressLint("StaticFieldLeak")
private static ImageView lastView;
public static void closeEndScreen(final ImageView imageView) {
if (!Settings.DISABLE_SUGGESTED_VIDEO_END_SCREEN.get()) return;
// Prevent adding the listener multiple times.
if (lastView == imageView) return;
lastView = imageView;
imageView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
if (imageView.isShown()) imageView.callOnClick();
});
}
}

View File

@@ -24,7 +24,7 @@ public final class EnableDebuggingPatch {
/**
* Injection point.
*/
public static boolean isBooleanFeatureFlagEnabled(boolean value, long flag) {
public static boolean isBooleanFeatureFlagEnabled(boolean value, Long flag) {
if (LOG_FEATURE_FLAGS && value) {
if (featureFlags.putIfAbsent(flag, true) == null) {
Logger.printDebug(() -> "boolean feature is enabled: " + flag);

View File

@@ -0,0 +1,13 @@
package app.revanced.extension.youtube.patches;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class HideEndScreenSuggestedVideoPatch {
/**
* Injection point.
*/
public static boolean hideEndScreenSuggestedVideo() {
return Settings.HIDE_END_SCREEN_SUGGESTED_VIDEO.get();
}
}

View File

@@ -21,6 +21,7 @@ public final class NavigationButtonsPatch {
{
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
put(NavigationButton.NOTIFICATIONS, Settings.HIDE_NOTIFICATIONS_BUTTON.get());
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
}

View File

@@ -1,8 +1,11 @@
package app.revanced.extension.youtube.patches;
import android.view.View;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
import app.revanced.extension.youtube.shared.VideoState;
@SuppressWarnings("unused")
@@ -24,4 +27,26 @@ public class PlayerTypeHookPatch {
VideoState.setFromString(youTubeVideoState.name());
}
/**
* Injection point.
*
* Add a listener to the shorts player overlay View.
* Triggered when a shorts player is attached or detached to Windows.
*
* @param view shorts player overlay (R.id.reel_watch_player).
*/
public static void onShortsCreate(View view) {
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(@Nullable View v) {
ShortsPlayerState.setOpen(true);
}
@Override
public void onViewDetachedFromWindow(@Nullable View v) {
ShortsPlayerState.setOpen(false);
}
});
}
}

View File

@@ -4,7 +4,6 @@ import static app.revanced.extension.youtube.returnyoutubedislike.ReturnYouTubeD
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.Build;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.Spanned;
@@ -366,9 +365,7 @@ public class ReturnYouTubeDislikePatch {
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();
private static void clearRemovedShortsTextViews() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // YouTube requires Android N or greater
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
}
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
}
/**

View File

@@ -1,9 +1,6 @@
package app.revanced.extension.youtube.patches;
import android.app.Activity;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.lang.ref.WeakReference;
import java.util.Objects;
@@ -58,7 +55,6 @@ public class ShortsAutoplayPatch {
/**
* @return If the app is currently in background PiP mode.
*/
@RequiresApi(api = Build.VERSION_CODES.N)
private static boolean isAppInBackgroundPiPMode() {
Activity activity = mainActivityRef.get();
return activity != null && activity.isInPictureInPictureMode();
@@ -80,7 +76,6 @@ public class ShortsAutoplayPatch {
/**
* Injection point.
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public static Enum<?> changeShortsRepeatBehavior(Enum<?> original) {
try {
final boolean autoplay;

View File

@@ -5,10 +5,13 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class VideoAdsPatch {
// Used by app.revanced.patches.youtube.ad.general.video.patch.VideoAdsPatch
// depends on Whitelist patch (still needs to be written)
private static final boolean SHOW_VIDEO_ADS = !Settings.HIDE_VIDEO_ADS.get();
/**
* Injection point.
*/
public static boolean shouldShowAds() {
return !Settings.HIDE_VIDEO_ADS.get(); // TODO && Whitelist.shouldShowAds();
return SHOW_VIDEO_ADS;
}
}

View File

@@ -7,13 +7,10 @@ import static app.revanced.extension.youtube.patches.announcements.requests.Anno
import android.app.Activity;
import android.app.AlertDialog;
import android.os.Build;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.widget.TextView;
import androidx.annotation.RequiresApi;
import org.json.JSONArray;
import java.io.IOException;
@@ -31,7 +28,6 @@ public final class AnnouncementsPatch {
private AnnouncementsPatch() {
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static boolean isLatestAlready() throws IOException {
HttpURLConnection connection =
AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(GET_LATEST_ANNOUNCEMENT_IDS);
@@ -70,7 +66,6 @@ public final class AnnouncementsPatch {
return Settings.ANNOUNCEMENT_LAST_ID.get() == id;
}
@RequiresApi(api = Build.VERSION_CODES.O)
public static void showAnnouncement(final Activity context) {
if (!Settings.ANNOUNCEMENTS.get()) return;

View File

@@ -8,10 +8,12 @@ import android.view.View;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.settings.Settings;
import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.StringTrieSearch;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class AdsFilter extends Filter {
@@ -22,14 +24,16 @@ public final class AdsFilter extends Filter {
// endregion
// https://encrypted-tbn0.gstatic.com/shopping?q=abc
private static final String STORE_BANNER_DOMAIN = "gstatic.com/shopping";
private static final boolean HIDE_END_SCREEN_STORE_BANNER =
Settings.HIDE_END_SCREEN_STORE_BANNER.get();
private final StringTrieSearch exceptions = new StringTrieSearch();
private final StringFilterGroup playerShoppingShelf;
private final ByteArrayFilterGroup playerShoppingShelfBuffer;
private final StringFilterGroup fullscreenOverlay;
private final ByteArrayFilterGroup endScreenStoreBannerBuffer;
private final StringFilterGroup channelProfile;
private final ByteArrayFilterGroup visitStoreButton;
@@ -69,7 +73,10 @@ public final class AdsFilter extends Filter {
"full_width_square_image_layout",
"video_display_button_group_layout",
"landscape_image_wide_button_layout",
"video_display_carousel_button_group_layout"
"video_display_carousel_button_group_layout",
"video_display_full_buttoned_short_dr_layout",
"compact_landscape_image_layout", // Tablet layout search results.
"text_image_no_button_layout" // Tablet layout search results.
);
final var generalAds = new StringFilterGroup(
@@ -116,32 +123,23 @@ public final class AdsFilter extends Filter {
);
playerShoppingShelf = new StringFilterGroup(
null,
Settings.HIDE_PLAYER_STORE_SHELF,
"horizontal_shelf.eml"
);
playerShoppingShelfBuffer = new ByteArrayFilterGroup(
Settings.HIDE_PLAYER_STORE_SHELF,
null,
"shopping_item_card_list.eml"
);
fullscreenOverlay = new StringFilterGroup(
Settings.HIDE_END_SCREEN_STORE_BANNER,
"fullscreen_overlay.eml"
);
endScreenStoreBannerBuffer = new ByteArrayFilterGroup(
null,
"gstatic.com/shopping"
);
channelProfile = new StringFilterGroup(
null,
"channel_profile.eml"
Settings.HIDE_VISIT_STORE_BUTTON,
"channel_profile.eml",
"page_header.eml"
);
visitStoreButton = new ByteArrayFilterGroup(
Settings.HIDE_VISIT_STORE_BUTTON,
null,
"header_store_button"
);
@@ -167,7 +165,6 @@ public final class AdsFilter extends Filter {
viewProducts,
selfSponsor,
fullscreenAd,
fullscreenOverlay,
channelProfile,
webLinkPanel,
shoppingLinks,
@@ -186,10 +183,8 @@ public final class AdsFilter extends Filter {
return false;
}
if (matchedGroup == fullscreenOverlay) {
if (contentIndex == 0 && endScreenStoreBannerBuffer.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
// Check for the index because of likelihood of false positives.
if (matchedGroup == shoppingLinks && contentIndex != 0) {
return false;
}
@@ -209,13 +204,25 @@ public final class AdsFilter extends Filter {
return false;
}
// Check for the index because of likelihood of false positives.
if (matchedGroup == shoppingLinks && contentIndex != 0)
return false;
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
/**
* Injection point.
*
* @param elementsList List of components of the end screen container.
* @param protobufList Component (ProtobufList).
*/
public static void hideEndScreenStoreBanner(List<Object> elementsList, Object protobufList) {
if (HIDE_END_SCREEN_STORE_BANNER && protobufList.toString().contains(STORE_BANNER_DOMAIN)) {
Logger.printDebug(() -> "Hiding store banner");
return;
}
elementsList.add(protobufList);
}
/**
* Hide the view, which shows ads in the homepage.
*

View File

@@ -2,20 +2,20 @@ package app.revanced.extension.youtube.patches.components;
import androidx.annotation.Nullable;
import app.revanced.extension.youtube.patches.playback.quality.RestoreOldVideoQualityMenuPatch;
import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch;
import app.revanced.extension.youtube.settings.Settings;
/**
* Abuse LithoFilter for {@link RestoreOldVideoQualityMenuPatch}.
* Abuse LithoFilter for {@link AdvancedVideoQualityMenuPatch}.
*/
public final class VideoQualityMenuFilterPatch extends Filter {
public final class AdvancedVideoQualityMenuFilter extends Filter {
// Must be volatile or synchronized, as litho filtering runs off main thread
// and this field is then access from the main thread.
public static volatile boolean isVideoQualityMenuVisible;
public VideoQualityMenuFilterPatch() {
public AdvancedVideoQualityMenuFilter() {
addPathCallbacks(new StringFilterGroup(
Settings.RESTORE_OLD_VIDEO_QUALITY_MENU,
Settings.ADVANCED_VIDEO_QUALITY_MENU,
"quick_quality_sheet_content.eml-js"
));
}

View File

@@ -6,8 +6,12 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
final class ButtonsFilter extends Filter {
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.eml";
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
private final StringFilterGroup likeSubscribeGlow;
private final StringFilterGroup actionBarGroup;
private final StringFilterGroup bufferFilterPathGroup;
private final ByteArrayFilterGroupList bufferButtonsGroupList = new ByteArrayFilterGroupList();
@@ -20,11 +24,19 @@ final class ButtonsFilter extends Filter {
addIdentifierCallbacks(actionBarGroup);
likeSubscribeGlow = new StringFilterGroup(
Settings.DISABLE_LIKE_SUBSCRIBE_GLOW,
"animated_button_border.eml"
);
bufferFilterPathGroup = new StringFilterGroup(
null,
"|ContainerType|button.eml|"
);
addPathCallbacks(
likeSubscribeGlow,
bufferFilterPathGroup,
new StringFilterGroup(
Settings.HIDE_LIKE_DISLIKE_BUTTON,
"|segmented_like_dislike_button"
@@ -40,8 +52,7 @@ final class ButtonsFilter extends Filter {
new StringFilterGroup(
Settings.HIDE_CLIP_BUTTON,
"|clip_button.eml|"
),
bufferFilterPathGroup
)
);
bufferButtonsGroupList.addAll(
@@ -83,6 +94,15 @@ final class ButtonsFilter extends Filter {
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == likeSubscribeGlow) {
if ((path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
&& path.contains(ANIMATED_VECTOR_TYPE_PATH)) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}
// If the current matched group is the action bar group,
// in case every filter group is enabled, hide the action bar.
if (matchedGroup == actionBarGroup) {

View File

@@ -1,9 +1,6 @@
package app.revanced.extension.youtube.patches.components;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.*;
import java.util.function.Consumer;
@@ -44,13 +41,11 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
return filterGroups.iterator();
}
@RequiresApi(api = Build.VERSION_CODES.N)
@Override
public void forEach(@NonNull Consumer<? super T> action) {
filterGroups.forEach(action);
}
@RequiresApi(api = Build.VERSION_CODES.N)
@NonNull
@Override
public Spliterator<T> spliterator() {

View File

@@ -4,11 +4,8 @@ import static app.revanced.extension.shared.StringRef.str;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import static java.lang.Character.UnicodeBlock.*;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
@@ -44,7 +41,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
* - When using whole word syntax, some keywords may need additional pluralized variations.
*/
@SuppressWarnings("unused")
@RequiresApi(api = Build.VERSION_CODES.N)
final class KeywordContentFilter extends Filter {
/**

View File

@@ -3,11 +3,9 @@ package app.revanced.extension.youtube.patches.components;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
@@ -18,10 +16,6 @@ import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public final class LayoutComponentsFilter extends Filter {
private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.eml";
private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.eml";
private static final String ANIMATED_VECTOR_TYPE_PATH = "AnimatedVectorType";
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch(
"V.ED", // Playlist browse id.
"java.lang.ref.WeakReference"
@@ -38,16 +32,15 @@ public final class LayoutComponentsFilter extends Filter {
private final StringTrieSearch exceptions = new StringTrieSearch();
private final StringFilterGroup inFeedSurvey;
private final StringFilterGroup notifyMe;
private final StringFilterGroup singleItemInformationPanel;
private final StringFilterGroup expandableMetadata;
private final ByteArrayFilterGroup searchResultRecommendations;
private final StringFilterGroup searchResultVideo;
private final StringFilterGroup compactChannelBarInner;
private final StringFilterGroup compactChannelBarInnerButton;
private final ByteArrayFilterGroup joinMembershipButton;
private final StringFilterGroup likeSubscribeGlow;
private final StringFilterGroup horizontalShelves;
@RequiresApi(api = Build.VERSION_CODES.N)
public LayoutComponentsFilter() {
exceptions.addPatterns(
"home_video_with_context",
@@ -81,7 +74,8 @@ public final class LayoutComponentsFilter extends Filter {
"text_post_root_slim.eml",
"post_base_wrapper_slim.eml",
"poll_post_root.eml",
"videos_post_root.eml"
"videos_post_root.eml",
"post_shelf_slim.eml"
);
final var communityGuidelines = new StringFilterGroup(
@@ -104,6 +98,11 @@ public final class LayoutComponentsFilter extends Filter {
"compact_banner"
);
final var subscriptionsChipBar = new StringFilterGroup(
Settings.HIDE_FILTER_BAR_FEED_IN_FEED,
"subscriptions_chip_bar"
);
inFeedSurvey = new StringFilterGroup(
Settings.HIDE_FEED_SURVEY,
"in_feed_survey",
@@ -122,8 +121,12 @@ public final class LayoutComponentsFilter extends Filter {
);
final var infoPanel = new StringFilterGroup(
Settings.HIDE_HIDE_INFO_PANELS,
"publisher_transparency_panel",
Settings.HIDE_INFO_PANELS,
"publisher_transparency_panel"
);
singleItemInformationPanel = new StringFilterGroup(
Settings.HIDE_INFO_PANELS,
"single_item_information_panel"
);
@@ -216,10 +219,6 @@ public final class LayoutComponentsFilter extends Filter {
"sponsorships"
);
likeSubscribeGlow = new StringFilterGroup(
Settings.DISABLE_LIKE_SUBSCRIBE_GLOW,
"animated_button_border.eml"
);
final var channelWatermark = new StringFilterGroup(
Settings.HIDE_VIDEO_CHANNEL_WATERMARK,
@@ -253,7 +252,6 @@ public final class LayoutComponentsFilter extends Filter {
expandableMetadata,
inFeedSurvey,
notifyMe,
likeSubscribeGlow,
compactChannelBar,
communityPosts,
paidPromotion,
@@ -268,8 +266,10 @@ public final class LayoutComponentsFilter extends Filter {
compactChannelBarInner,
medicalPanel,
infoPanel,
singleItemInformationPanel,
emergencyBox,
subscribersCommunityGuidelines,
subscriptionsChipBar,
channelGuidelines,
audioTrackButton,
artistCard,
@@ -284,6 +284,19 @@ public final class LayoutComponentsFilter extends Filter {
@Override
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// This identifier is used not only in players but also in search results:
// https://github.com/ReVanced/revanced-patches/issues/3245
// Until 2024, medical information panels such as Covid 19 also used this identifier and were shown in the search results.
// From 2025, the medical information panel is no longer shown in the search results.
// Therefore, this identifier does not filter when the search bar is activated.
if (matchedGroup == singleItemInformationPanel) {
if (PlayerType.getCurrent().isMaximizedOrFullscreen() || !NavigationBar.isSearchBarActive()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}
if (matchedGroup == searchResultVideo) {
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
@@ -291,15 +304,6 @@ public final class LayoutComponentsFilter extends Filter {
return false;
}
if (matchedGroup == likeSubscribeGlow) {
if ((path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))
&& path.contains(ANIMATED_VECTOR_TYPE_PATH)) {
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
}
return false;
}
// The groups are excluded from the filter due to the exceptions list below.
// Filter them separately here.
if (matchedGroup == notifyMe || matchedGroup == inFeedSurvey || matchedGroup == expandableMetadata)

View File

@@ -1,9 +1,6 @@
package app.revanced.extension.youtube.patches.components;
import android.os.Build;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
@@ -16,7 +13,6 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
private final ByteArrayFilterGroup exception;
private final StringFilterGroup videoQualityMenuFooter;
@RequiresApi(api = Build.VERSION_CODES.N)
public PlayerFlyoutMenuItemsFilter() {
exception = new ByteArrayFilterGroup(
// Whitelist Quality menu item when "Hide Additional settings menu" is enabled

View File

@@ -8,30 +8,30 @@ import android.widget.ListView;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.patches.components.VideoQualityMenuFilterPatch;
import app.revanced.extension.youtube.patches.components.AdvancedVideoQualityMenuFilter;
import app.revanced.extension.youtube.settings.Settings;
/**
* This patch contains the logic to show the old video quality menu.
* This patch contains the logic to always open the advanced video quality menu.
* Two methods are required, because the quality menu is a RecyclerView in the new YouTube version
* and a ListView in the old one.
*/
@SuppressWarnings("unused")
public final class RestoreOldVideoQualityMenuPatch {
public final class AdvancedVideoQualityMenuPatch {
/**
* Injection point.
*/
public static void onFlyoutMenuCreate(RecyclerView recyclerView) {
if (!Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get()) return;
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
recyclerView.getViewTreeObserver().addOnDrawListener(() -> {
try {
// Check if the current view is the quality menu.
if (!VideoQualityMenuFilterPatch.isVideoQualityMenuVisible || recyclerView.getChildCount() == 0) {
if (!AdvancedVideoQualityMenuFilter.isVideoQualityMenuVisible || recyclerView.getChildCount() == 0) {
return;
}
VideoQualityMenuFilterPatch.isVideoQualityMenuVisible = false;
AdvancedVideoQualityMenuFilter.isVideoQualityMenuVisible = false;
ViewParent quickQualityViewParent = Utils.getParentView(recyclerView, 3);
if (!(quickQualityViewParent instanceof ViewGroup)) {
@@ -39,16 +39,15 @@ public final class RestoreOldVideoQualityMenuPatch {
}
View firstChild = recyclerView.getChildAt(0);
if (!(firstChild instanceof ViewGroup)) {
if (!(firstChild instanceof ViewGroup firstChildGroup)) {
return;
}
ViewGroup advancedQualityParentView = (ViewGroup) firstChild;
if (advancedQualityParentView.getChildCount() < 4) {
if (firstChildGroup.getChildCount() < 4) {
return;
}
View advancedQualityView = advancedQualityParentView.getChildAt(3);
View advancedQualityView = firstChildGroup.getChildAt(3);
if (advancedQualityView == null) {
return;
}
@@ -71,7 +70,7 @@ public final class RestoreOldVideoQualityMenuPatch {
* Used to force the creation of the advanced menu item for the Shorts quality flyout.
*/
public static boolean forceAdvancedVideoQualityMenuCreation(boolean original) {
return Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get() || original;
return Settings.ADVANCED_VIDEO_QUALITY_MENU.get() || original;
}
/**
@@ -79,8 +78,8 @@ public final class RestoreOldVideoQualityMenuPatch {
*
* Used if spoofing to an old app version, and also used for the Shorts video quality flyout.
*/
public static void showOldVideoQualityMenu(final ListView listView) {
if (!Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get()) return;
public static void showAdvancedVideoQualityMenu(ListView listView) {
if (!Settings.ADVANCED_VIDEO_QUALITY_MENU.get()) return;
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override

View File

@@ -12,15 +12,19 @@ import java.util.List;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.IntegerSetting;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.ShortsPlayerState;
@SuppressWarnings("unused")
public class RememberVideoQualityPatch {
private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2;
private static final IntegerSetting wifiQualitySetting = Settings.VIDEO_QUALITY_DEFAULT_WIFI;
private static final IntegerSetting mobileQualitySetting = Settings.VIDEO_QUALITY_DEFAULT_MOBILE;
private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI;
private static final IntegerSetting videoQualityMobile = Settings.VIDEO_QUALITY_DEFAULT_MOBILE;
private static final IntegerSetting shortsQualityWifi = Settings.SHORTS_QUALITY_DEFAULT_WIFI;
private static final IntegerSetting shortsQualityMobile = Settings.SHORTS_QUALITY_DEFAULT_MOBILE;
private static boolean qualityNeedsUpdating;
@@ -41,17 +45,29 @@ public class RememberVideoQualityPatch {
@Nullable
private static List<Integer> videoQualities;
private static boolean shouldRememberVideoQuality() {
BooleanSetting preference = ShortsPlayerState.isOpen() ?
Settings.REMEMBER_SHORTS_QUALITY_LAST_SELECTED
: Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED;
return preference.get();
}
private static void changeDefaultQuality(int defaultQuality) {
String networkTypeMessage;
boolean useShortsPreference = ShortsPlayerState.isOpen();
if (Utils.getNetworkType() == NetworkType.MOBILE) {
mobileQualitySetting.save(defaultQuality);
if (useShortsPreference) shortsQualityMobile.save(defaultQuality);
else videoQualityMobile.save(defaultQuality);
networkTypeMessage = str("revanced_remember_video_quality_mobile");
} else {
wifiQualitySetting.save(defaultQuality);
if (useShortsPreference) shortsQualityWifi.save(defaultQuality);
else videoQualityWifi.save(defaultQuality);
networkTypeMessage = str("revanced_remember_video_quality_wifi");
}
Utils.showToastShort(
str("revanced_remember_video_quality_toast", networkTypeMessage, (defaultQuality + "p")));
Utils.showToastShort(str(
useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast",
networkTypeMessage, (defaultQuality + "p")
));
}
/**
@@ -62,9 +78,10 @@ public class RememberVideoQualityPatch {
*/
public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, Object qInterface, String qIndexMethod) {
try {
boolean useShortsPreference = ShortsPlayerState.isOpen();
final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE
? mobileQualitySetting.get()
: wifiQualitySetting.get();
? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get()
: (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get();
if (!userChangedDefaultQuality && preferredQuality == AUTOMATIC_VIDEO_QUALITY_VALUE) {
return originalQualityIndex; // Nothing to do.
@@ -141,17 +158,17 @@ public class RememberVideoQualityPatch {
* Injection point. Old quality menu.
*/
public static void userChangedQuality(int selectedQualityIndex) {
if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) return;
userSelectedQualityIndex = selectedQualityIndex;
userChangedDefaultQuality = true;
if (shouldRememberVideoQuality()) {
userSelectedQualityIndex = selectedQualityIndex;
userChangedDefaultQuality = true;
}
}
/**
* Injection point. New quality menu.
*/
public static void userChangedQualityInNewFlyout(int selectedQuality) {
if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.get()) return;
if (!shouldRememberVideoQuality()) return;
changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080).
}

View File

@@ -6,11 +6,19 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.AnimatedVectorDrawable;
import com.airbnb.lottie.LottieAnimationView;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Locale;
import java.util.Scanner;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
@@ -93,17 +101,6 @@ public final class SeekbarColorPatch {
return customSeekbarColor;
}
/**
* Injection point
*/
public static boolean useLotteLaunchSplashScreen(boolean original) {
Logger.printDebug(() -> "useLotteLaunchSplashScreen original: " + original);
if (SEEKBAR_CUSTOM_COLOR_ENABLED) return false;
return original;
}
private static int colorChannelTo3Bits(int channel8Bits) {
final float channel3Bits = channel8Bits * 7 / 255f;
@@ -127,6 +124,17 @@ public final class SeekbarColorPatch {
/**
* Injection point
*/
public static boolean useLotteLaunchSplashScreen(boolean original) {
// This method is only used for development purposes to force the old style launch screen.
// Forcing this off on some devices can cause unexplained startup crashes,
// where the lottie animation is still used even though this condition appears to bypass it.
return original; // false = drawable style, true = lottie style.
}
/**
* Injection point.
* Old drawable style launch screen.
*/
public static void setSplashAnimationDrawableTheme(AnimatedVectorDrawable vectorDrawable) {
// Alternatively a ColorMatrixColorFilter can be used to change the color of the drawable
// without using any styles, but a color filter cannot selectively change the seekbar
@@ -134,6 +142,8 @@ public final class SeekbarColorPatch {
// Even if the seekbar color xml value is changed to a completely different color (such as green),
// a color filter still cannot be selectively applied when the drawable has more than 1 color.
try {
// Must set the color even if custom seekbar is off,
// because the xml color was replaced with a themed value.
String seekbarStyle = get9BitStyleIdentifier(customSeekbarColor);
Logger.printDebug(() -> "Using splash seekbar style: " + seekbarStyle);
@@ -154,6 +164,77 @@ public final class SeekbarColorPatch {
}
}
/**
* Injection point.
* Modern Lottie style animation.
*/
public static void setSplashAnimationLottie(LottieAnimationView view, int resourceId) {
try {
if (!SEEKBAR_CUSTOM_COLOR_ENABLED) {
view.patch_setAnimation(resourceId);
return;
}
//noinspection ConstantConditions
if (false) { // Set true to force slow animation for development.
final int longAnimation = Utils.getResourceIdentifier(
Utils.isDarkModeEnabled(Utils.getContext())
? "startup_animation_5s_30fps_dark"
: "startup_animation_5s_30fps_light",
"raw");
if (longAnimation != 0) {
resourceId = longAnimation;
}
}
// Must specify primary key name otherwise the morphing YT logo color is also changed.
String originalKey = "\"k\":";
String originalPrimary = originalKey + "[1,0,0.2,1]";
String originalAccent = originalKey + "[1,0.152941176471,0.56862745098,1]";
String replacementPrimary = originalKey + getColorStringArray(customSeekbarColor);
String replacementAccent = originalKey + getColorStringArray(customSeekbarColorGradient[1]);
String json = loadRawResourceAsString(resourceId);
if (json == null) {
return; // Should never happen.
}
if (BaseSettings.DEBUG.get() && (!json.contains(originalPrimary) || !json.contains(originalAccent))) {
String jsonFinal = json;
Logger.printException(() -> "Could not replace launch animation colors: " + jsonFinal);
}
Logger.printDebug(() -> "Replacing Lottie animation JSON");
json = json.replace(originalPrimary, replacementPrimary);
json = json.replace(originalAccent, replacementAccent);
// cacheKey is not needed since the animation will not be reused.
view.patch_setAnimation(new ByteArrayInputStream(json.getBytes()), null);
} catch (Exception ex) {
Logger.printException(() -> "setSplashAnimationLottie failure", ex);
}
}
private static String getColorStringArray(int color) {
return Arrays.toString(new double[]{
Color.red(color) / 255.0,
Color.green(color) / 255.0,
Color.blue(color) / 255.0,
Color.alpha(color) / 255.0
});
}
private static String loadRawResourceAsString(int resourceId) {
try (InputStream inputStream = Utils.getContext().getResources().openRawResource(resourceId);
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8.name()).useDelimiter("\\A")) {
return scanner.next();
} catch (IOException e) {
Logger.printException(() -> "Could not load resource: " + resourceId);
return null;
}
}
/**
* Injection point.
*/

View File

@@ -234,6 +234,12 @@ public class ReturnYouTubeDislike {
// example video: https://www.youtube.com/watch?v=UnrU5vxCHxw
// RYD data: https://returnyoutubedislikeapi.com/votes?videoId=UnrU5vxCHxw
//
if (!Settings.RYD_ESTIMATED_LIKE.get()) {
// Change the "Likes" string to show that likes and dislikes are hidden.
String hiddenMessageString = str("revanced_ryd_video_likes_hidden_by_video_owner");
return newSpanUsingStylingOfAnotherSpan(oldSpannable, hiddenMessageString);
}
Logger.printDebug(() -> "Using estimated likes");
oldLikes = formatDislikeCount(voteData.getLikeCount());
}
@@ -346,56 +352,52 @@ public class ReturnYouTubeDislike {
}
private static String formatDislikeCount(long dislikeCount) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize
if (dislikeCountFormatter == null) {
Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale;
dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT);
synchronized (ReturnYouTubeDislike.class) { // Number formatter is not thread safe.
if (dislikeCountFormatter == null) {
// Must use default locale and not Utils context locale,
// otherwise if using a different settings language then the
// formatting will use that of the different language.
Locale locale = Locale.getDefault();
dislikeCountFormatter = CompactDecimalFormat.getInstance(locale, CompactDecimalFormat.CompactStyle.SHORT);
// YouTube disregards locale specific number characters
// and instead shows english number characters everywhere.
// To use the same behavior, override the digit characters to use English
// so languages such as Arabic will show "1.234" instead of the native "۱,۲۳٤"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings());
dislikeCountFormatter.setDecimalFormatSymbols(symbols);
}
// YouTube disregards locale specific number characters
// and instead shows English number characters everywhere.
// To use the same behavior, override the digit characters to use English
// so languages such as Arabic will show "1.234" instead of the native "۱,۲۳٤"
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings());
dislikeCountFormatter.setDecimalFormatSymbols(symbols);
}
return dislikeCountFormatter.format(dislikeCount);
}
}
// Will never be reached, as the oldest supported YouTube app requires Android N or greater.
return String.valueOf(dislikeCount);
return dislikeCountFormatter.format(dislikeCount);
}
}
private static String formatDislikePercentage(float dislikePercentage) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
synchronized (ReturnYouTubeDislike.class) { // number formatter is not thread safe, must synchronize
if (dislikePercentageFormatter == null) {
Locale locale = Objects.requireNonNull(Utils.getContext()).getResources().getConfiguration().locale;
dislikePercentageFormatter = NumberFormat.getPercentInstance(locale);
synchronized (ReturnYouTubeDislike.class) { // Number formatter is not thread safe, must synchronize.
if (dislikePercentageFormatter == null) {
Locale locale = Locale.getDefault();
dislikePercentageFormatter = NumberFormat.getPercentInstance(locale);
// Want to set the digit strings, and the simplest way is to cast to the implementation NumberFormat returns.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& dislikePercentageFormatter instanceof DecimalFormat) {
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings());
((DecimalFormat) dislikePercentageFormatter).setDecimalFormatSymbols(symbols);
}
// Want to set the digit strings, and the simplest way is to cast to the implementation NumberFormat returns.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
&& dislikePercentageFormatter instanceof DecimalFormat decimalFormatter) {
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
symbols.setDigitStrings(DecimalFormatSymbols.getInstance(Locale.ENGLISH).getDigitStrings());
decimalFormatter.setDecimalFormatSymbols(symbols);
}
if (dislikePercentage >= 0.01) { // at least 1%
dislikePercentageFormatter.setMaximumFractionDigits(0); // show only whole percentage points
} else {
dislikePercentageFormatter.setMaximumFractionDigits(1); // show up to 1 digit precision
}
return dislikePercentageFormatter.format(dislikePercentage);
}
}
// Will never be reached, as the oldest supported YouTube app requires Android N or greater.
return String.valueOf((int) (dislikePercentage * 100));
if (dislikePercentage >= 0.01) { // at least 1%
dislikePercentageFormatter.setMaximumFractionDigits(0); // show only whole percentage points
} else {
dislikePercentageFormatter.setMaximumFractionDigits(1); // show up to 1 digit precision
}
return dislikePercentageFormatter.format(dislikePercentage);
}
}
@NonNull
@@ -403,15 +405,13 @@ public class ReturnYouTubeDislike {
Objects.requireNonNull(videoId);
synchronized (fetchCache) {
// Remove any expired entries.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
final long now = System.currentTimeMillis();
fetchCache.values().removeIf(value -> {
final boolean expired = value.isExpired(now);
if (expired)
Logger.printDebug(() -> "Removing expired fetch: " + value.videoId);
return expired;
});
}
final long now = System.currentTimeMillis();
fetchCache.values().removeIf(value -> {
final boolean expired = value.isExpired(now);
if (expired)
Logger.printDebug(() -> "Removing expired fetch: " + value.videoId);
return expired;
});
ReturnYouTubeDislike fetch = fetchCache.get(videoId);
if (fetch == null) {
@@ -551,6 +551,15 @@ public class ReturnYouTubeDislike {
}
if (spanIsForLikes) {
if (!Utils.containsNumber(original)) {
if (!Settings.RYD_ESTIMATED_LIKE.get()) {
Logger.printDebug(() -> "Likes are hidden");
return original;
} else {
Logger.printDebug(() -> "Using estimated likes");
}
}
// Scrolling Shorts does not cause the Spans to be reloaded,
// so there is no need to cache the likes for this situations.
Logger.printDebug(() -> "Creating likes span for: " + votingData.videoId);

View File

@@ -5,15 +5,12 @@ import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.preference.PreferenceFragment;
import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.RequiresApi;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
@@ -22,6 +19,7 @@ import app.revanced.extension.shared.settings.AppLanguage;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.youtube.ThemeHelper;
import app.revanced.extension.youtube.patches.VersionCheckPatch;
import app.revanced.extension.youtube.patches.spoof.SpoofAppVersionPatch;
import app.revanced.extension.youtube.settings.preference.ReVancedPreferenceFragment;
import app.revanced.extension.youtube.settings.preference.ReturnYouTubeDislikePreferenceFragment;
import app.revanced.extension.youtube.settings.preference.SponsorBlockPreferenceFragment;
@@ -66,6 +64,10 @@ public class LicenseActivityHook {
if (Settings.RESTORE_OLD_SETTINGS_MENUS.get()) {
return false;
}
// Spoofing can cause half broken settings menus of old and new settings.
if (SpoofAppVersionPatch.isSpoofingToLessThan("19.35.36")) {
return false;
}
// On the first launch of a clean install, forcing the cairo menu can give a
// half broken appearance because all the preference icons may not be available yet.
@@ -79,7 +81,6 @@ public class LicenseActivityHook {
* <p>
* Hooks LicenseActivity#onCreate in order to inject our own fragment.
*/
@RequiresApi(api = Build.VERSION_CODES.N)
public static void initialize(Activity licenseActivity) {
try {
ThemeHelper.setActivityTheme(licenseActivity);
@@ -119,15 +120,13 @@ public class LicenseActivityHook {
}
}
@RequiresApi(api = Build.VERSION_CODES.N)
@SuppressLint("UseCompatLoadingForDrawables")
private static void createToolbar(Activity activity, String toolbarTitleResourceName) {
// Replace dummy placeholder toolbar.
// This is required to fix submenu title alignment issue with Android ASOP 15+
ViewGroup toolBarParent = activity.findViewById(
getResourceIdentifier("revanced_toolbar_parent", "id"));
ViewGroup dummyToolbar = toolBarParent.findViewById(getResourceIdentifier(
"revanced_toolbar", "id"));
ViewGroup dummyToolbar = Utils.getChildViewByResourceName(toolBarParent,"revanced_toolbar");
toolbarLayoutParams = dummyToolbar.getLayoutParams();
toolBarParent.removeView(dummyToolbar);

View File

@@ -47,11 +47,14 @@ import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings;
public class Settings extends BaseSettings {
// Video
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_video_quality_default_wifi", -2);
public static final IntegerSetting VIDEO_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_video_quality_default_mobile", -2);
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", FALSE);
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_WIFI = new IntegerSetting("revanced_shorts_quality_default_wifi", -2, true);
public static final IntegerSetting SHORTS_QUALITY_DEFAULT_MOBILE = new IntegerSetting("revanced_shorts_quality_default_mobile", -2, true);
public static final BooleanSetting REMEMBER_SHORTS_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_shorts_quality_last_selected", FALSE);
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE);
public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE);
// Speed
public static final FloatSetting SPEED_TAP_AND_HOLD = new FloatSetting("revanced_speed_tap_and_hold", 2.0f, true);
public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", FALSE);
@@ -64,7 +67,7 @@ public class Settings extends BaseSettings {
// Ads
public static final BooleanSetting HIDE_BUTTONED_ADS = new BooleanSetting("revanced_hide_buttoned_ads", TRUE);
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", FALSE);
public static final BooleanSetting HIDE_END_SCREEN_STORE_BANNER = new BooleanSetting("revanced_hide_end_screen_store_banner", TRUE, true);
public static final BooleanSetting HIDE_FULLSCREEN_ADS = new BooleanSetting("revanced_hide_fullscreen_ads", TRUE);
public static final BooleanSetting HIDE_GENERAL_ADS = new BooleanSetting("revanced_hide_general_ads", TRUE);
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
@@ -124,9 +127,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting COPY_VIDEO_URL = new BooleanSetting("revanced_copy_video_url", FALSE);
public static final BooleanSetting COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_copy_video_url_timestamp", TRUE);
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE, true);
public static final EnumSetting<FullscreenMode> EXIT_FULLSCREEN = new EnumSetting<>("revanced_exit_fullscreen", FullscreenMode.DISABLED);
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
@@ -136,8 +137,9 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_community_guidelines", TRUE);
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
public static final BooleanSetting HIDE_ENDSCREEN_CARDS = new BooleanSetting("revanced_hide_endscreen_cards", FALSE);
public static final BooleanSetting HIDE_END_SCREEN_SUGGESTED_VIDEO = new BooleanSetting("revanced_end_screen_suggested_video", FALSE, true);
public static final BooleanSetting HIDE_HIDE_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_channel_guidelines", TRUE);
public static final BooleanSetting HIDE_HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
public static final BooleanSetting HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", FALSE);
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
@@ -172,10 +174,10 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_COMMENTS_CHAT_SUMMARY = new BooleanSetting("revanced_hide_comments_chat_summary", FALSE);
public static final BooleanSetting HIDE_COMMENTS_BY_MEMBERS_HEADER = new BooleanSetting("revanced_hide_comments_by_members_header", FALSE);
public static final BooleanSetting HIDE_COMMENTS_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_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_THANKS_BUTTON = new BooleanSetting("revanced_hide_comments_thanks_button", TRUE);
public static final BooleanSetting HIDE_COMMENTS_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comments_timestamp_and_emoji_buttons", TRUE);
// Description
public static final BooleanSetting HIDE_ATTRIBUTES_SECTION = new BooleanSetting("revanced_hide_attributes_section", FALSE);
public static final BooleanSetting HIDE_CHAPTERS_SECTION = new BooleanSetting("revanced_hide_chapters_section", TRUE);
@@ -185,6 +187,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
// Action buttons
public static final BooleanSetting DISABLE_LIKE_SUBSCRIBE_GLOW = new BooleanSetting("revanced_disable_like_subscribe_glow", FALSE);
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
@@ -228,7 +231,9 @@ public class Settings extends BaseSettings {
public static final BooleanSetting HIDE_SHORTS_BUTTON = new BooleanSetting("revanced_hide_shorts_button", TRUE, true);
public static final BooleanSetting HIDE_SUBSCRIPTIONS_BUTTON = new BooleanSetting("revanced_hide_subscriptions_button", FALSE, true);
public static final BooleanSetting HIDE_NAVIGATION_BUTTON_LABELS = new BooleanSetting("revanced_hide_navigation_button_labels", FALSE, true);
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true);
public static final BooleanSetting HIDE_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_hide_notifications_button", FALSE, true);
public static final BooleanSetting SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true,
"revanced_switch_create_with_notifications_button_user_dialog_message");
public static final BooleanSetting DISABLE_TRANSLUCENT_STATUS_BAR = new BooleanSetting("revanced_disable_translucent_status_bar", FALSE, true);
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_LIGHT = new BooleanSetting("revanced_disable_translucent_navigation_bar_light", FALSE, true);
public static final BooleanSetting DISABLE_TRANSLUCENT_NAVIGATION_BAR_DARK = new BooleanSetting("revanced_disable_translucent_navigation_bar_dark", FALSE, true);
@@ -282,7 +287,6 @@ public class Settings extends BaseSettings {
"revanced_seekbar_thumbnails_high_quality_dialog_message", new SeekbarThumbnailsHighQualityAvailability());
public static final BooleanSetting SLIDE_TO_SEEK = new BooleanSetting("revanced_slide_to_seek", FALSE, true);
public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", FALSE, true);
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
public static final StringSetting SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_primary", "#FF0033", true, parent(SEEKBAR_CUSTOM_COLOR));
public static final StringSetting SEEKBAR_CUSTOM_COLOR_ACCENT = new StringSetting("revanced_seekbar_custom_color_accent", "#FF2791", true, parent(SEEKBAR_CUSTOM_COLOR));
@@ -306,19 +310,19 @@ public class Settings extends BaseSettings {
// Swipe controls
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true);
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", FALSE);
public static final BooleanSetting SWIPE_VOLUME = new BooleanSetting("revanced_swipe_volume", FALSE);
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", FALSE, true);
public static final BooleanSetting SWIPE_VOLUME = new BooleanSetting("revanced_swipe_volume", FALSE, true);
public static final BooleanSetting SWIPE_PRESS_TO_ENGAGE = new BooleanSetting("revanced_swipe_press_to_engage", FALSE, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final BooleanSetting SWIPE_HAPTIC_FEEDBACK = new BooleanSetting("revanced_swipe_haptic_feedback", TRUE, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127, true,
public static final BooleanSetting SWIPE_SHOW_CIRCULAR_OVERLAY = new BooleanSetting("revanced_swipe_show_circular_overlay", FALSE, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
// Debugging
public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 22, true,
public static final BooleanSetting SWIPE_OVERLAY_MINIMAL_STYLE = new BooleanSetting("revanced_swipe_overlay_minimal_style", FALSE, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_OVERLAY_OPACITY = new IntegerSetting("revanced_swipe_overlay_background_opacity", 60, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final LongSetting SWIPE_OVERLAY_TIMEOUT = new LongSetting("revanced_swipe_overlay_timeout", 500L, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
@@ -332,6 +336,7 @@ public class Settings extends BaseSettings {
public static final BooleanSetting RYD_SHORTS = new BooleanSetting("ryd_shorts", TRUE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_DISLIKE_PERCENTAGE = new BooleanSetting("ryd_dislike_percentage", FALSE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_COMPACT_LAYOUT = new BooleanSetting("ryd_compact_layout", FALSE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_ESTIMATED_LIKE = new BooleanSetting("ryd_estimated_like", TRUE, parent(RYD_ENABLED));
public static final BooleanSetting RYD_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("ryd_toast_on_connection_error", TRUE, parent(RYD_ENABLED));
// SponsorBlock
@@ -343,13 +348,14 @@ public class Settings extends BaseSettings {
public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED));
public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED));
public static final BooleanSetting SB_CREATE_NEW_SEGMENT = new BooleanSetting("sb_create_new_segment", FALSE, parent(SB_ENABLED));
public static final BooleanSetting SB_SQUARE_LAYOUT = new BooleanSetting("sb_square_layout", FALSE, parent(SB_ENABLED));
public static final BooleanSetting SB_COMPACT_SKIP_BUTTON = new BooleanSetting("sb_compact_skip_button", FALSE, parent(SB_ENABLED));
public static final BooleanSetting SB_AUTO_HIDE_SKIP_BUTTON = new BooleanSetting("sb_auto_hide_skip_button", TRUE, parent(SB_ENABLED));
public static final BooleanSetting SB_TOAST_ON_SKIP = new BooleanSetting("sb_toast_on_skip", TRUE, parent(SB_ENABLED));
public static final BooleanSetting SB_TOAST_ON_CONNECTION_ERROR = new BooleanSetting("sb_toast_on_connection_error", TRUE, parent(SB_ENABLED));
public static final BooleanSetting SB_TRACK_SKIP_COUNT = new BooleanSetting("sb_track_skip_count", TRUE, parent(SB_ENABLED));
public static final FloatSetting SB_SEGMENT_MIN_DURATION = new FloatSetting("sb_min_segment_duration", 0F, parent(SB_ENABLED));
public static final BooleanSetting SB_VIDEO_LENGTH_WITHOUT_SEGMENTS = new BooleanSetting("sb_video_length_without_segments", TRUE, parent(SB_ENABLED));
public static final BooleanSetting SB_VIDEO_LENGTH_WITHOUT_SEGMENTS = new BooleanSetting("sb_video_length_without_segments", FALSE, parent(SB_ENABLED));
public static final StringSetting SB_API_URL = new StringSetting("sb_api_url", "https://sponsor.ajay.app");
public static final BooleanSetting SB_USER_IS_VIP = new BooleanSetting("sb_user_is_vip", FALSE);
public static final IntegerSetting SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS = new IntegerSetting("sb_local_time_saved_number_segments", 0);
@@ -379,9 +385,13 @@ public class Settings extends BaseSettings {
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFF");
// Deprecated migrations
public static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024
private static final StringSetting DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING = new StringSetting("uuid", ""); // Delete sometime in 2024
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE, true);
private static final BooleanSetting DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", FALSE);
private static final IntegerSetting DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA = new IntegerSetting("revanced_swipe_overlay_background_alpha", 127);
private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033");
private static final BooleanSetting DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE);
private static final BooleanSetting DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
static {
// region Migration
@@ -400,11 +410,9 @@ public class Settings extends BaseSettings {
migrateOldSettingToNew(DEPRECATED_HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER, HIDE_PLAYER_FLYOUT_VIDEO_QUALITY_FOOTER);
// Old spoof versions that no longer work reliably.
if (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0) {
Logger.printInfo(() -> "Resetting spoof app version target");
SPOOF_APP_VERSION_TARGET.resetToDefault();
}
migrateOldSettingToNew(DEPRECATED_DISABLE_SUGGESTED_VIDEO_END_SCREEN, HIDE_END_SCREEN_SUGGESTED_VIDEO);
migrateOldSettingToNew(DEPRECATED_RESTORE_OLD_VIDEO_QUALITY_MENU, ADVANCED_VIDEO_QUALITY_MENU);
// Migrate renamed enum.
//noinspection deprecation
@@ -436,6 +444,11 @@ public class Settings extends BaseSettings {
DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY.resetToDefault();
}
if (!DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.isSetToDefault()) {
SWIPE_OVERLAY_OPACITY.save(DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.get() / 255);
DEPRECATED_SWIPE_OVERLAY_BACKGROUND_ALPHA.resetToDefault();
}
// endregion
// region SB import/export callbacks

View File

@@ -3,18 +3,14 @@ package app.revanced.extension.youtube.settings.preference;
import static android.text.Html.FROM_HTML_MODE_COMPACT;
import android.content.Context;
import android.os.Build;
import android.preference.Preference;
import android.text.Html;
import android.util.AttributeSet;
import androidx.annotation.RequiresApi;
/**
* Allows using basic html for the summary text.
*/
@SuppressWarnings({"unused", "deprecation"})
@RequiresApi(api = Build.VERSION_CODES.O)
public class HtmlPreference extends Preference {
{
setSummary(Html.fromHtml(getSummary().toString(), FROM_HTML_MODE_COMPACT));

View File

@@ -17,8 +17,6 @@ import android.view.WindowInsets;
import android.widget.TextView;
import android.widget.Toolbar;
import androidx.annotation.RequiresApi;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -98,7 +96,6 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
listPreference.setEntryValues(sortedEntryValues);
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
protected void initialize() {
super.initialize();
@@ -153,13 +150,10 @@ public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
toolbar.setTitle(childScreen.getTitle());
toolbar.setNavigationIcon(getBackButtonDrawable());
toolbar.setNavigationOnClickListener(view -> preferenceScreenDialog.dismiss());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
final int margin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
);
toolbar.setTitleMargin(margin, 0, margin, 0);
}
final int margin = (int) TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, 16, getResources().getDisplayMetrics()
);
toolbar.setTitleMargin(margin, 0, margin, 0);
TextView toolbarTextView = Utils.getChildView(toolbar,
true, TextView.class::isInstance);

View File

@@ -39,6 +39,11 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
*/
private SwitchPreference compactLayoutPreference;
/**
* If hidden likes are replaced with an estimated value.
*/
private SwitchPreference estimatedLikesPreference;
/**
* If segmented like/dislike button uses smaller compact layout.
*/
@@ -48,6 +53,7 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
shortsPreference.setEnabled(Settings.RYD_SHORTS.isAvailable());
percentagePreference.setEnabled(Settings.RYD_DISLIKE_PERCENTAGE.isAvailable());
compactLayoutPreference.setEnabled(Settings.RYD_COMPACT_LAYOUT.isAvailable());
estimatedLikesPreference.setEnabled(Settings.RYD_ESTIMATED_LIKE.isAvailable());
toastOnRYDNotAvailable.setEnabled(Settings.RYD_TOAST_ON_CONNECTION_ERROR.isAvailable());
}
@@ -117,6 +123,19 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
});
preferenceScreen.addPreference(compactLayoutPreference);
estimatedLikesPreference = new SwitchPreference(context);
estimatedLikesPreference.setChecked(Settings.RYD_ESTIMATED_LIKE.get());
estimatedLikesPreference.setTitle(str("revanced_ryd_estimated_like_title"));
estimatedLikesPreference.setSummaryOn(str("revanced_ryd_estimated_like_summary_on"));
estimatedLikesPreference.setSummaryOff(str("revanced_ryd_estimated_like_summary_off"));
estimatedLikesPreference.setOnPreferenceChangeListener((pref, newValue) -> {
Settings.RYD_ESTIMATED_LIKE.save((Boolean) newValue);
ReturnYouTubeDislike.clearAllUICaches();
updateUIState();
return true;
});
preferenceScreen.addPreference(estimatedLikesPreference);
toastOnRYDNotAvailable = new SwitchPreference(context);
toastOnRYDNotAvailable.setChecked(Settings.RYD_TOAST_ON_CONNECTION_ERROR.get());
toastOnRYDNotAvailable.setTitle(str("revanced_ryd_toast_on_connection_error_title"));

View File

@@ -6,7 +6,6 @@ import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.*;
import android.text.Html;
@@ -37,8 +36,9 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
private SwitchPreference sbEnabled;
private SwitchPreference addNewSegment;
private SwitchPreference votingEnabled;
private SwitchPreference compactSkipButton;
private SwitchPreference autoHideSkipSegmentButton;
private SwitchPreference compactSkipButton;
private SwitchPreference squareLayout;
private SwitchPreference showSkipToast;
private SwitchPreference trackSkips;
private SwitchPreference showTimeWithoutSegments;
@@ -62,7 +62,9 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
} else if (!Settings.SB_CREATE_NEW_SEGMENT.get()) {
SponsorBlockViewController.hideNewSegmentLayout();
}
// Voting and add new segment buttons automatically shows/hide themselves.
// Voting and add new segment buttons automatically show/hide themselves.
SponsorBlockViewController.updateLayout();
sbEnabled.setChecked(enabled);
@@ -72,11 +74,14 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
votingEnabled.setChecked(Settings.SB_VOTING_BUTTON.get());
votingEnabled.setEnabled(enabled);
autoHideSkipSegmentButton.setEnabled(enabled);
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
compactSkipButton.setChecked(Settings.SB_COMPACT_SKIP_BUTTON.get());
compactSkipButton.setEnabled(enabled);
autoHideSkipSegmentButton.setChecked(Settings.SB_AUTO_HIDE_SKIP_BUTTON.get());
autoHideSkipSegmentButton.setEnabled(enabled);
squareLayout.setChecked(Settings.SB_SQUARE_LAYOUT.get());
squareLayout.setEnabled(enabled);
showSkipToast.setChecked(Settings.SB_TOAST_ON_SKIP.get());
showSkipToast.setEnabled(enabled);
@@ -176,6 +181,17 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
return true;
});
autoHideSkipSegmentButton = new SwitchPreference(context);
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
category.addPreference(autoHideSkipSegmentButton);
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
updateUI();
return true;
});
compactSkipButton = new SwitchPreference(context);
compactSkipButton.setTitle(str("revanced_sb_enable_compact_skip_button"));
compactSkipButton.setSummaryOn(str("revanced_sb_enable_compact_skip_button_sum_on"));
@@ -187,13 +203,13 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
return true;
});
autoHideSkipSegmentButton = new SwitchPreference(context);
autoHideSkipSegmentButton.setTitle(str("revanced_sb_enable_auto_hide_skip_segment_button"));
autoHideSkipSegmentButton.setSummaryOn(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_on"));
autoHideSkipSegmentButton.setSummaryOff(str("revanced_sb_enable_auto_hide_skip_segment_button_sum_off"));
category.addPreference(autoHideSkipSegmentButton);
autoHideSkipSegmentButton.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_AUTO_HIDE_SKIP_BUTTON.save((Boolean) newValue);
squareLayout = new SwitchPreference(context);
squareLayout.setTitle(str("revanced_sb_square_layout"));
squareLayout.setSummaryOn(str("revanced_sb_square_layout_sum_on"));
squareLayout.setSummaryOff(str("revanced_sb_square_layout_sum_off"));
category.addPreference(squareLayout);
squareLayout.setOnPreferenceChangeListener((preference1, newValue) -> {
Settings.SB_SQUARE_LAYOUT.save((Boolean) newValue);
updateUI();
return true;
});
@@ -393,9 +409,7 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
importExport.getEditText().setAutofillHints((String) null);
}
importExport.getEditText().setAutofillHints((String) null);
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
importExport.setOnPreferenceClickListener(preference1 -> {
importExport.getEditText().setText(SponsorBlockSettings.exportDesktopSettings());

View File

@@ -85,10 +85,12 @@ public class SpoofStreamingDataSideEffectsPreference extends Preference {
String summary = str(key + "_summary");
// Android VR supports AV1 but all other clients do not.
if (clientType != ClientType.ANDROID_VR && clientType != ClientType.ANDROID_VR_NO_AUTH) {
if (clientType != ClientType.ANDROID_VR_AUTH && clientType != ClientType.ANDROID_VR_NO_AUTH) {
summary += '\n' + str("revanced_spoof_video_streams_about_no_av1");
}
summary += '\n' + str("revanced_spoof_video_streams_about_kids_videos");
setTitle(title);
setSummary(summary);
}

View File

@@ -3,11 +3,11 @@ package app.revanced.extension.youtube.shared;
import static app.revanced.extension.youtube.shared.NavigationBar.NavigationButton.CREATE;
import android.app.Activity;
import android.os.Build;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.lang.ref.WeakReference;
import java.util.Arrays;
@@ -26,12 +26,22 @@ import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public final class NavigationBar {
/**
* Interface to call obfuscated methods in AppCompat Toolbar class.
*/
public interface AppCompatToolbarPatchInterface {
Drawable patch_getNavigationIcon();
}
//
// Search bar
// Search and toolbar.
//
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
private static volatile WeakReference<AppCompatToolbarPatchInterface> toolbarResultsRef
= new WeakReference<>(null);
/**
* Injection point.
*/
@@ -39,6 +49,22 @@ public final class NavigationBar {
searchBarResultsRef = new WeakReference<>(searchbarResults);
}
/**
* Injection point.
*/
public static void setToolbar(FrameLayout layout) {
AppCompatToolbarPatchInterface toolbar = Utils.getChildView(layout, false, (view) ->
view instanceof AppCompatToolbarPatchInterface
);
if (toolbar == null) {
Logger.printException(() -> "Could not find navigation toolbar");
return;
}
toolbarResultsRef = new WeakReference<>(toolbar);
}
/**
* @return If the search bar is on screen. This includes if the player
* is on screen and the search results are behind the player (and not visible).
@@ -49,8 +75,13 @@ public final class NavigationBar {
return searchbarResults != null && searchbarResults.getParent() != null;
}
public static boolean isBackButtonVisible() {
AppCompatToolbarPatchInterface toolbar = toolbarResultsRef.get();
return toolbar != null && toolbar.patch_getNavigationIcon() != null;
}
//
// Navigation bar buttons
// Navigation bar buttons.
//
/**
@@ -257,7 +288,6 @@ public final class NavigationBar {
* Injection point.
* Fixes missing drawable.
*/
@RequiresApi(api = Build.VERSION_CODES.N)
@SuppressWarnings({"unchecked", "rawtypes"})
public static void setCairoNotificationFilledIcon(EnumMap enumMap, Enum tabActivityCairo) {
if (fillBellCairoBlack != 0) {

View File

@@ -5,7 +5,7 @@ import app.revanced.extension.youtube.Event
import app.revanced.extension.youtube.patches.VideoInformation
/**
* Main player type.
* Regular player type.
*/
enum class PlayerType {
/**
@@ -21,9 +21,6 @@ enum class PlayerType {
/**
* A regular video is minimized.
*
* When spoofing to 16.x YouTube and watching a short with a regular video in the background,
* the type can be this (and not [HIDDEN]).
*/
WATCH_WHILE_MINIMIZED,
WATCH_WHILE_MAXIMIZED,
@@ -56,8 +53,7 @@ enum class PlayerType {
val newType = nameToPlayerType[enumName]
if (newType == null) {
Logger.printException { "Unknown PlayerType encountered: $enumName" }
} else if (current != newType) {
Logger.printDebug { "PlayerType changed to: $newType" }
} else {
current = newType
}
}
@@ -68,9 +64,13 @@ enum class PlayerType {
@JvmStatic
var current
get() = currentPlayerType
private set(value) {
currentPlayerType = value
onChange(currentPlayerType)
private set(type) {
if (currentPlayerType != type) {
Logger.printDebug { "Changed to: $type" }
currentPlayerType = type
onChange(type)
}
}
@Volatile // Read/write from different threads.
@@ -90,8 +90,6 @@ enum class PlayerType {
* Does not include the first moment after a short is opened when a regular video is minimized on screen,
* or while watching a short with a regular video present on a spoofed 16.x version of YouTube.
* To include those situations instead use [isNoneHiddenOrMinimized].
*
* @see VideoInformation
*/
fun isNoneOrHidden(): Boolean {
return this == NONE || this == HIDDEN
@@ -107,8 +105,11 @@ enum class PlayerType {
* when spoofing to an old version this will return false even
* though a Short is being opened or is on screen (see [isNoneHiddenOrMinimized]).
*
* Instead of this method, consider using {@link ShortsPlayerState}
* which may work better for some situations.
*
* @return If nothing, a Short, or a regular video is sliding off screen to a dismissed or hidden state.
* @see VideoInformation
* @see ShortsPlayerState
*/
fun isNoneHiddenOrSlidingMinimized(): Boolean {
return isNoneOrHidden() || this == WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED
@@ -125,9 +126,12 @@ enum class PlayerType {
* Typically used to detect if a Short is playing when the player cannot be in a minimized state,
* such as the user interacting with a button or element of the player.
*
* Instead of this method, consider using {@link ShortsPlayerState}
* which may work better for some situations.
*
* @return If nothing, a Short, a regular video is sliding off screen to a dismissed or hidden state,
* a regular video is minimized (and a new video is not being opened).
* @see VideoInformation
* @see ShortsPlayerState
*/
fun isNoneHiddenOrMinimized(): Boolean {
return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED

View File

@@ -0,0 +1,38 @@
package app.revanced.extension.youtube.shared
import app.revanced.extension.shared.Logger
import app.revanced.extension.youtube.Event
/**
* Shorts player state.
*/
class ShortsPlayerState {
companion object {
@JvmStatic
fun setOpen(open: Boolean) {
if (isOpen != open) {
Logger.printDebug { "ShortsPlayerState open changed to: $isOpen" }
isOpen = open
onChange(open)
}
}
@Volatile
private var isOpen = false
/**
* Shorts player state change listener.
*/
@JvmStatic
val onChange = Event<Boolean>()
/**
* If the Shorts player is currently open.
*/
@JvmStatic
fun isOpen(): Boolean {
return isOpen
}
}
}

View File

@@ -0,0 +1,56 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import android.view.View;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
public class CreateSegmentButton {
@Nullable
private static PlayerControlButton instance;
public static void hideControls() {
if (instance != null) instance.hide();
}
/**
* injection point
*/
public static void initialize(View controlsView) {
try {
instance = new PlayerControlButton(
controlsView,
"revanced_sb_create_segment_button",
null,
CreateSegmentButton::shouldBeShown,
v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility(),
null
);
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Injection point
*/
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) instance.setVisibilityImmediate(visible);
}
/**
* Injection point
*/
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) instance.setVisibility(visible, animated);
}
private static boolean shouldBeShown() {
return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get()
&& !VideoInformation.isAtEndOfVideo();
}
}

View File

@@ -1,111 +0,0 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.view.View;
import android.widget.ImageView;
import java.lang.ref.WeakReference;
import java.util.Objects;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
// Edit: This should be a subclass of PlayerControlButton
public class CreateSegmentButtonController {
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
private static boolean isShowing;
/**
* injection point
*/
public static void initialize(View youtubeControlsLayout) {
try {
Logger.printDebug(() -> "initializing new segment button");
ImageView imageView = Objects.requireNonNull(Utils.getChildViewByResourceName(
youtubeControlsLayout, "revanced_sb_create_segment_button"));
imageView.setVisibility(View.GONE);
imageView.setOnClickListener(v -> SponsorBlockViewController.toggleNewSegmentLayoutVisibility());
buttonReference = new WeakReference<>(imageView);
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* injection point
*/
public static void changeVisibilityImmediate(boolean visible) {
if (visible) {
// Fix button flickering, by pushing this call to the back of
// the main thread and letting other layout code run first.
Utils.runOnMainThread(() -> setVisibility(true, false));
} else {
setVisibility(false, false);
}
}
/**
* injection point
*/
public static void changeVisibility(boolean visible, boolean animated) {
// Ignore this call, otherwise with full screen thumbnails the buttons are visible while seeking.
if (visible && !animated) return;
setVisibility(visible, animated);
}
private static void setVisibility(boolean visible, boolean animated) {
try {
if (isShowing == visible) return;
isShowing = visible;
ImageView iView = buttonReference.get();
if (iView == null) return;
if (visible) {
iView.clearAnimation();
if (!shouldBeShown()) {
return;
}
if (animated) {
iView.startAnimation(PlayerControlButton.getButtonFadeIn());
}
iView.setVisibility(View.VISIBLE);
return;
}
if (iView.getVisibility() == View.VISIBLE) {
iView.clearAnimation();
if (animated) {
iView.startAnimation(PlayerControlButton.getButtonFadeOut());
}
iView.setVisibility(View.GONE);
}
} catch (Exception ex) {
Logger.printException(() -> "changeVisibility failure", ex);
}
}
private static boolean shouldBeShown() {
return Settings.SB_ENABLED.get() && Settings.SB_CREATE_NEW_SEGMENT.get()
&& !VideoInformation.isAtEndOfVideo();
}
public static void hide() {
if (!isShowing) {
return;
}
Utils.verifyOnMainThread();
View v = buttonReference.get();
if (v == null) {
return;
}
v.setVisibility(View.GONE);
isShowing = false;
}
}

View File

@@ -2,10 +2,11 @@ package app.revanced.extension.youtube.sponsorblock.ui;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
@@ -14,15 +15,15 @@ import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
import app.revanced.extension.shared.Logger;
import static app.revanced.extension.shared.Utils.getResourceColor;
import static app.revanced.extension.shared.Utils.getResourceDimensionPixelSize;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
public final class NewSegmentLayout extends FrameLayout {
private static final ColorStateList rippleColorStateList = new ColorStateList(
new int[][]{new int[]{android.R.attr.state_enabled}},
new int[]{0x33ffffff} // sets the ripple color to white
new int[]{0x33ffffff} // Ripple effect color (semi-transparent white)
);
private final int rippleEffectId;
final int defaultBottomMargin;
final int ctaBottomMargin;
@@ -47,10 +48,6 @@ public final class NewSegmentLayout extends FrameLayout {
getResourceIdentifier(context, "revanced_sb_new_segment", "layout"), this, true
);
TypedValue rippleEffect = new TypedValue();
context.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, rippleEffect, true);
rippleEffectId = rippleEffect.resourceId;
initializeButton(
context,
"revanced_sb_new_segment_rewind",
@@ -120,6 +117,28 @@ public final class NewSegmentLayout extends FrameLayout {
});
}
/**
* Update the layout of this UI control.
*/
public void updateLayout() {
final boolean squareLayout = Settings.SB_SQUARE_LAYOUT.get();
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) getLayoutParams();
final int margin = squareLayout
? 0
: SponsorBlockViewController.ROUNDED_LAYOUT_MARGIN;
params.setMarginStart(margin);
setLayoutParams(params);
GradientDrawable backgroundDrawable = new GradientDrawable();
backgroundDrawable.setColor(getResourceColor("skip_ad_button_background_color"));
final float cornerRadius = squareLayout
? 0
: 16 * getResources().getDisplayMetrics().density;
backgroundDrawable.setCornerRadius(cornerRadius);
setBackground(backgroundDrawable);
}
@FunctionalInterface
private interface ButtonOnClickHandlerFunction {
void apply();

View File

@@ -8,6 +8,7 @@ import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -19,11 +20,19 @@ import androidx.annotation.NonNull;
import java.util.Objects;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
public class SkipSponsorButton extends FrameLayout {
private static final boolean highContrast = true;
/**
* Adds a high contrast border around the skip button.
*
* This feature is not currently used.
* If this is added, it needs an additional button width change because
* as-is the skip button text is clipped when this is on.
*/
private static final boolean highContrast = false;
private final LinearLayout skipSponsorBtnContainer;
private final TextView skipSponsorTextView;
private final Paint background;
@@ -49,18 +58,23 @@ public class SkipSponsorButton extends FrameLayout {
LayoutInflater.from(context).inflate(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button", "layout"), this, true); // layout:skip_ad_button
setMinimumHeight(getResourceDimensionPixelSize("ad_skip_ad_button_min_height")); // dimen:ad_skip_ad_button_min_height
skipSponsorBtnContainer = Objects.requireNonNull((LinearLayout) findViewById(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button_container", "id"))); // id:skip_ad_button_container
skipSponsorBtnContainer = Objects.requireNonNull(findViewById(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button_container", "id"))); // id:skip_ad_button_container
background = new Paint();
background.setColor(getResourceColor("skip_ad_button_background_color")); // color:skip_ad_button_background_color);
background.setStyle(Paint.Style.FILL);
border = new Paint();
border.setColor(getResourceColor("skip_ad_button_border_color")); // color:skip_ad_button_border_color);
border.setStrokeWidth(getResourceDimension("ad_skip_ad_button_border_width")); // dimen:ad_skip_ad_button_border_width);
border.setStyle(Paint.Style.STROKE);
skipSponsorTextView = Objects.requireNonNull((TextView) findViewById(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button_text", "id"))); // id:skip_ad_button_text;
skipSponsorTextView = Objects.requireNonNull(findViewById(getResourceIdentifier(context, "revanced_sb_skip_sponsor_button_text", "id"))); // id:skip_ad_button_text;
defaultBottomMargin = getResourceDimensionPixelSize("skip_button_default_bottom_margin"); // dimen:skip_button_default_bottom_margin
ctaBottomMargin = getResourceDimensionPixelSize("skip_button_cta_bottom_margin"); // dimen:skip_button_cta_bottom_margin
updateLayout();
skipSponsorBtnContainer.setOnClickListener(v -> {
// The view controller handles hiding this button, but hide it here as well just in case something goofs.
setVisibility(View.GONE);
@@ -72,30 +86,56 @@ public class SkipSponsorButton extends FrameLayout {
protected final void dispatchDraw(Canvas canvas) {
final int left = skipSponsorBtnContainer.getLeft();
final int top = skipSponsorBtnContainer.getTop();
final int leftPlusWidth = (left + skipSponsorBtnContainer.getWidth());
final int topPlusHeight = (top + skipSponsorBtnContainer.getHeight());
canvas.drawRect(left, top, leftPlusWidth, topPlusHeight, background);
if (!highContrast) {
canvas.drawLines(new float[]{
leftPlusWidth, top, left, top,
left, top, left, topPlusHeight,
left, topPlusHeight, leftPlusWidth, topPlusHeight},
border);
final int right = left + skipSponsorBtnContainer.getWidth();
final int bottom = top + skipSponsorBtnContainer.getHeight();
// Determine corner radius for rounded button
float cornerRadius = skipSponsorBtnContainer.getHeight() / 2f;
if (Settings.SB_SQUARE_LAYOUT.get()) {
// Square button.
canvas.drawRect(left, top, right, bottom, background);
if (highContrast) {
canvas.drawLines(new float[]{
right, top, left, top,
left, top, left, bottom,
left, bottom, right, bottom},
border); // Draw square border.
}
} else {
// Rounded button.
RectF rect = new RectF(left, top, right, bottom);
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, background); // Draw rounded background.
if (highContrast) {
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, border); // Draw rounded border.
}
}
super.dispatchDraw(canvas);
}
/**
* @return true, if this button state was changed
* Update the layout of this button.
*/
public boolean updateSkipButtonText(@NonNull SponsorSegment segment) {
public void updateLayout() {
if (Settings.SB_SQUARE_LAYOUT.get()) {
// No padding for square corners.
setPadding(0, 0, 0, 0);
} else {
// Apply padding for rounded corners.
final int padding = SponsorBlockViewController.ROUNDED_LAYOUT_MARGIN;
setPadding(padding, 0, padding, 0);
}
}
public void updateSkipButtonText(@NonNull SponsorSegment segment) {
this.segment = segment;
CharSequence newText = segment.getSkipButtonText();
//noinspection StringEqualsCharSequence
if (newText.equals(skipSponsorTextView.getText())) {
return false;
return;
}
skipSponsorTextView.setText(newText);
return true;
}
}

View File

@@ -19,8 +19,11 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType;
import app.revanced.extension.youtube.sponsorblock.objects.SponsorSegment;
import kotlin.Unit;
public class SponsorBlockViewController {
public static final int ROUNDED_LAYOUT_MARGIN = 12;
private static WeakReference<RelativeLayout> inlineSponsorOverlayRef = new WeakReference<>(null);
private static WeakReference<ViewGroup> youtubeOverlaysLayoutRef = new WeakReference<>(null);
private static WeakReference<SkipSponsorButton> skipHighlightButtonRef = new WeakReference<>(null);
@@ -36,7 +39,7 @@ public class SponsorBlockViewController {
static {
PlayerType.getOnChange().addObserver((PlayerType type) -> {
playerTypeChanged(type);
return null;
return Unit.INSTANCE;
});
}
@@ -80,12 +83,16 @@ public class SponsorBlockViewController {
});
youtubeOverlaysLayoutRef = new WeakReference<>(viewGroup);
skipHighlightButtonRef = new WeakReference<>(
Objects.requireNonNull(layout.findViewById(getResourceIdentifier("revanced_sb_skip_highlight_button", "id"))));
skipSponsorButtonRef = new WeakReference<>(
Objects.requireNonNull(layout.findViewById(getResourceIdentifier("revanced_sb_skip_sponsor_button", "id"))));
newSegmentLayoutRef = new WeakReference<>(
Objects.requireNonNull(layout.findViewById(getResourceIdentifier("revanced_sb_new_segment_view", "id"))));
skipHighlightButtonRef = new WeakReference<>(Objects.requireNonNull(
layout.findViewById(getResourceIdentifier("revanced_sb_skip_highlight_button", "id"))));
skipSponsorButtonRef = new WeakReference<>(Objects.requireNonNull(
layout.findViewById(getResourceIdentifier("revanced_sb_skip_sponsor_button", "id"))));
NewSegmentLayout newSegmentLayout = Objects.requireNonNull(
layout.findViewById(getResourceIdentifier("revanced_sb_new_segment_view", "id")));
newSegmentLayoutRef = new WeakReference<>(newSegmentLayout);
newSegmentLayout.updateLayout();
newSegmentLayoutVisible = false;
skipHighlight = null;
@@ -101,6 +108,23 @@ public class SponsorBlockViewController {
hideNewSegmentLayout();
}
public static void updateLayout() {
SkipSponsorButton button = skipSponsorButtonRef.get();
if (button != null) {
button.updateLayout();
}
button = skipHighlightButtonRef.get();
if (button != null) {
button.updateLayout();
}
NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get();
if (newSegmentLayout != null) {
newSegmentLayout.updateLayout();
}
}
public static void showSkipHighlightButton(@NonNull SponsorSegment segment) {
skipHighlight = Objects.requireNonNull(segment);
NewSegmentLayout newSegmentLayout = newSegmentLayoutRef.get();
@@ -214,8 +238,8 @@ public class SponsorBlockViewController {
// but if buttons are showing when the end of the video is reached then they need
// to be forcefully hidden
if (!Settings.AUTO_REPEAT.get()) {
CreateSegmentButtonController.hide();
VotingButtonController.hide();
CreateSegmentButton.hideControls();
VotingButton.hideControls();
}
} catch (Exception ex) {
Logger.printException(() -> "endOfVideoReached failure", ex);

View File

@@ -0,0 +1,58 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import android.view.View;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
public class VotingButton {
@Nullable
private static PlayerControlButton instance;
public static void hideControls() {
if (instance != null) instance.hide();
}
/**
* injection point
*/
public static void initialize(View controlsView) {
try {
instance = new PlayerControlButton(
controlsView,
"revanced_sb_voting_button",
null,
VotingButton::shouldBeShown,
v -> SponsorBlockUtils.onVotingClicked(v.getContext()),
null
);
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* Injection point
*/
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) instance.setVisibilityImmediate(visible);
}
/**
* Injection point
*/
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) instance.setVisibility(visible, animated);
}
private static boolean shouldBeShown() {
return Settings.SB_ENABLED.get() && Settings.SB_VOTING_BUTTON.get()
&& SegmentPlaybackController.videoHasSegments() && !VideoInformation.isAtEndOfVideo();
}
}

View File

@@ -1,116 +0,0 @@
package app.revanced.extension.youtube.sponsorblock.ui;
import static app.revanced.extension.shared.Utils.getResourceIdentifier;
import android.view.View;
import android.widget.ImageView;
import java.lang.ref.WeakReference;
import java.util.Objects;
import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.sponsorblock.SegmentPlaybackController;
import app.revanced.extension.youtube.sponsorblock.SponsorBlockUtils;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.youtube.videoplayer.PlayerControlButton;
// Edit: This should be a subclass of PlayerControlButton
public class VotingButtonController {
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
private static boolean isShowing;
/**
* injection point
*/
public static void initialize(View youtubeControlsLayout) {
try {
Logger.printDebug(() -> "initializing voting button");
ImageView imageView = Objects.requireNonNull(Utils.getChildViewByResourceName(
youtubeControlsLayout, "revanced_sb_voting_button"));
imageView.setVisibility(View.GONE);
imageView.setOnClickListener(v -> SponsorBlockUtils.onVotingClicked(v.getContext()));
buttonReference = new WeakReference<>(imageView);
} catch (Exception ex) {
Logger.printException(() -> "initialize failure", ex);
}
}
/**
* injection point
*/
public static void changeVisibilityImmediate(boolean visible) {
if (visible) {
// Fix button flickering, by pushing this call to the back of
// the main thread and letting other layout code run first.
Utils.runOnMainThread(() -> setVisibility(true, false));
} else {
setVisibility(false, false);
}
}
/**
* injection point
*/
public static void changeVisibility(boolean visible, boolean animated) {
// Ignore this call, otherwise with full screen thumbnails the buttons are visible while seeking.
if (visible && !animated) return;
setVisibility(visible, animated);
}
/**
* injection point
*/
private static void setVisibility(boolean visible, boolean animated) {
try {
if (isShowing == visible) return;
isShowing = visible;
ImageView iView = buttonReference.get();
if (iView == null) return;
if (visible) {
iView.clearAnimation();
if (!shouldBeShown()) {
return;
}
if (animated) {
iView.startAnimation(PlayerControlButton.getButtonFadeIn());
}
iView.setVisibility(View.VISIBLE);
return;
}
if (iView.getVisibility() == View.VISIBLE) {
iView.clearAnimation();
if (animated) {
iView.startAnimation(PlayerControlButton.getButtonFadeOut());
}
iView.setVisibility(View.GONE);
}
} catch (Exception ex) {
Logger.printException(() -> "changeVisibility failure", ex);
}
}
private static boolean shouldBeShown() {
return Settings.SB_ENABLED.get() && Settings.SB_VOTING_BUTTON.get()
&& SegmentPlaybackController.videoHasSegments() && !VideoInformation.isAtEndOfVideo();
}
public static void hide() {
if (!isShowing) {
return;
}
Utils.verifyOnMainThread();
View v = buttonReference.get();
if (v == null) {
return;
}
v.setVisibility(View.GONE);
isShowing = false;
}
}

View File

@@ -2,6 +2,8 @@ package app.revanced.extension.youtube.swipecontrols
import android.content.Context
import android.graphics.Color
import app.revanced.extension.shared.StringRef.str
import app.revanced.extension.shared.Utils
import app.revanced.extension.youtube.settings.Settings
import app.revanced.extension.youtube.shared.PlayerType
@@ -18,19 +20,17 @@ class SwipeControlsConfigurationProvider(
* should swipe controls be enabled? (global setting)
*/
val enableSwipeControls: Boolean
get() = isFullscreenVideo && (enableVolumeControls || enableBrightnessControl)
get() = (enableVolumeControls || enableBrightnessControl) && isFullscreenVideo
/**
* should swipe controls for volume be enabled?
*/
val enableVolumeControls: Boolean
get() = Settings.SWIPE_VOLUME.get()
val enableVolumeControls = Settings.SWIPE_VOLUME.get()
/**
* should swipe controls for volume be enabled?
*/
val enableBrightnessControl: Boolean
get() = Settings.SWIPE_BRIGHTNESS.get()
val enableBrightnessControl = Settings.SWIPE_BRIGHTNESS.get()
/**
* is the video player currently in fullscreen mode?
@@ -44,7 +44,7 @@ class SwipeControlsConfigurationProvider(
* should volume key controls be overwritten? (global setting)
*/
val overwriteVolumeKeyControls: Boolean
get() = isFullscreenVideo && enableVolumeControls
get() = enableVolumeControls && isFullscreenVideo
//endregion
//region gesture adjustments
@@ -63,7 +63,6 @@ class SwipeControlsConfigurationProvider(
//endregion
//region overlay adjustments
/**
* should the overlay enable haptic feedback?
*/
@@ -77,23 +76,52 @@ class SwipeControlsConfigurationProvider(
get() = Settings.SWIPE_OVERLAY_TIMEOUT.get()
/**
* text size for the overlay, in sp
* Gets the opacity value (0-100%) is converted to an alpha value (0-255) for transparency.
* If the opacity value is out of range, it resets to the default and displays a warning message.
*/
val overlayTextSize: Int
get() = Settings.SWIPE_OVERLAY_TEXT_SIZE.get()
val overlayBackgroundOpacity: Int
get() {
var opacity = Settings.SWIPE_OVERLAY_OPACITY.get()
if (opacity < 0 || opacity > 100) {
Utils.showToastLong(str("revanced_swipe_overlay_background_opacity_invalid_toast"))
Settings.SWIPE_OVERLAY_OPACITY.resetToDefault()
opacity = Settings.SWIPE_OVERLAY_OPACITY.get()
}
opacity = opacity * 255 / 100
return Color.argb(opacity, 0, 0, 0)
}
/**
* get the background color for text on the overlay, as a color int
* The color of the progress overlay.
*/
val overlayTextBackgroundColor: Int
get() = Color.argb(Settings.SWIPE_OVERLAY_BACKGROUND_ALPHA.get(), 0, 0, 0)
val overlayProgressColor: Int
get() = 0xBFFFFFFF.toInt()
/**
* get the foreground color for text on the overlay, as a color int
* The color used for the background of the progress overlay fill.
*/
val overlayForegroundColor: Int
val overlayFillBackgroundPaint: Int
get() = 0x80D3D3D3.toInt()
/**
* The color used for the text and icons in the overlay.
*/
val overlayTextColor: Int
get() = Color.WHITE
/**
* A flag that determines if the overlay should only show the icon.
*/
val overlayShowOverlayMinimalStyle: Boolean
get() = Settings.SWIPE_OVERLAY_MINIMAL_STYLE.get()
/**
* A flag that determines if the progress bar should be circular.
*/
val isCircularProgressBar: Boolean
get() = Settings.SWIPE_SHOW_CIRCULAR_OVERLAY.get()
//endregion
//region behaviour

View File

@@ -82,11 +82,15 @@ abstract class BaseGestureController(
}
override fun onScroll(
from: MotionEvent,
from: MotionEvent?,
to: MotionEvent,
distanceX: Float,
distanceY: Float,
): Boolean {
if (from == null) {
return false
}
// submit to swipe detector
submitForSwipe(from, to, distanceX, distanceY)

View File

@@ -1,138 +1,145 @@
package app.revanced.extension.youtube.swipecontrols.views
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.os.Handler
import android.os.Looper
import android.util.TypedValue
import android.util.AttributeSet
import android.view.HapticFeedbackConstants
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
import android.widget.TextView
import app.revanced.extension.shared.StringRef.str
import app.revanced.extension.shared.Utils
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider
import app.revanced.extension.youtube.swipecontrols.misc.SwipeControlsOverlay
import app.revanced.extension.youtube.swipecontrols.misc.applyDimension
import kotlin.math.min
import kotlin.math.round
/**
* main overlay layout for volume and brightness swipe controls
*
* @param context context to create in
* Main overlay layout for displaying volume and brightness level with both circular and horizontal progress bars.
*/
class SwipeControlsOverlayLayout(
context: Context,
private val config: SwipeControlsConfigurationProvider,
) : RelativeLayout(context), SwipeControlsOverlay {
/**
* DO NOT use this, for tools only
*/
constructor(context: Context) : this(context, SwipeControlsConfigurationProvider(context))
private val feedbackTextView: TextView
private val autoBrightnessIcon: Drawable
private val manualBrightnessIcon: Drawable
private val mutedVolumeIcon: Drawable
private val normalVolumeIcon: Drawable
// Drawable icons for brightness and volume
private val autoBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_auto")
private val lowBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_low")
private val mediumBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_medium")
private val highBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_high")
private val fullBrightnessIcon: Drawable = getDrawable("revanced_ic_sc_brightness_full")
private val mutedVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_mute")
private val lowVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_low")
private val normalVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_normal")
private val fullVolumeIcon: Drawable = getDrawable("revanced_ic_sc_volume_high")
private fun getDrawable(name: String, width: Int, height: Int): Drawable {
return resources.getDrawable(
// Function to retrieve drawable resources by name
private fun getDrawable(name: String): Drawable {
val drawable = resources.getDrawable(
Utils.getResourceIdentifier(context, name, "drawable"),
context.theme,
).apply {
setTint(config.overlayForegroundColor)
setBounds(
0,
0,
width,
height,
)
}
)
drawable.setTint(config.overlayTextColor)
return drawable
}
// Initialize progress bars
private val circularProgressView: CircularProgressView
private val horizontalProgressView: HorizontalProgressView
init {
// init views
val feedbackTextViewPadding = 2.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP)
val compoundIconPadding = 4.applyDimension(context, TypedValue.COMPLEX_UNIT_DIP)
feedbackTextView = TextView(context).apply {
layoutParams = LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
).apply {
// Initialize circular progress bar
circularProgressView = CircularProgressView(
context,
config.overlayBackgroundOpacity,
config.overlayShowOverlayMinimalStyle,
config.overlayProgressColor,
config.overlayFillBackgroundPaint,
config.overlayTextColor
).apply {
layoutParams = LayoutParams(300, 300).apply {
addRule(CENTER_IN_PARENT, TRUE)
setPadding(
feedbackTextViewPadding,
feedbackTextViewPadding,
feedbackTextViewPadding,
feedbackTextViewPadding,
)
}
background = GradientDrawable().apply {
cornerRadius = 8f
setColor(config.overlayTextBackgroundColor)
}
setTextColor(config.overlayForegroundColor)
setTextSize(TypedValue.COMPLEX_UNIT_SP, config.overlayTextSize.toFloat())
compoundDrawablePadding = compoundIconPadding
visibility = GONE
visibility = GONE // Initially hidden
}
addView(feedbackTextView)
addView(circularProgressView)
// get icons scaled, assuming square icons
val iconHeight = round(feedbackTextView.lineHeight * .8).toInt()
autoBrightnessIcon = getDrawable("revanced_ic_sc_brightness_auto", iconHeight, iconHeight)
manualBrightnessIcon = getDrawable("revanced_ic_sc_brightness_manual", iconHeight, iconHeight)
mutedVolumeIcon = getDrawable("revanced_ic_sc_volume_mute", iconHeight, iconHeight)
normalVolumeIcon = getDrawable("revanced_ic_sc_volume_normal", iconHeight, iconHeight)
// Initialize horizontal progress bar
val screenWidth = resources.displayMetrics.widthPixels
val layoutWidth = (screenWidth * 2 / 3).toInt() // 2/3 of screen width
horizontalProgressView = HorizontalProgressView(
context,
config.overlayBackgroundOpacity,
config.overlayShowOverlayMinimalStyle,
config.overlayProgressColor,
config.overlayFillBackgroundPaint,
config.overlayTextColor
).apply {
layoutParams = LayoutParams(layoutWidth, 100).apply {
addRule(CENTER_HORIZONTAL)
topMargin = 40 // Top margin
}
visibility = GONE // Initially hidden
}
addView(horizontalProgressView)
}
// Handler and callback for hiding progress bars
private val feedbackHideHandler = Handler(Looper.getMainLooper())
private val feedbackHideCallback = Runnable {
feedbackTextView.visibility = GONE
circularProgressView.visibility = GONE
horizontalProgressView.visibility = GONE
}
/**
* show the feedback view for a given time
*
* @param message the message to show
* @param icon the icon to use
* Displays the progress bar with the appropriate value, icon, and type (brightness or volume).
*/
private fun showFeedbackView(message: String, icon: Drawable) {
private fun showFeedbackView(value: String, progress: Int, max: Int, icon: Drawable, isBrightness: Boolean) {
feedbackHideHandler.removeCallbacks(feedbackHideCallback)
feedbackHideHandler.postDelayed(feedbackHideCallback, config.overlayShowTimeoutMillis)
feedbackTextView.apply {
text = message
setCompoundDrawablesRelative(
icon,
null,
null,
null,
)
val viewToShow = if (config.isCircularProgressBar) circularProgressView else horizontalProgressView
viewToShow.apply {
setProgress(progress, max, value, isBrightness)
this.icon = icon
visibility = VISIBLE
}
}
// Handle volume change
override fun onVolumeChanged(newVolume: Int, maximumVolume: Int) {
showFeedbackView(
"$newVolume",
if (newVolume > 0) normalVolumeIcon else mutedVolumeIcon,
)
val volumePercentage = (newVolume.toFloat() / maximumVolume) * 100
val icon = when {
newVolume == 0 -> mutedVolumeIcon
volumePercentage < 33 -> lowVolumeIcon
volumePercentage < 66 -> normalVolumeIcon
else -> fullVolumeIcon
}
showFeedbackView("$newVolume", newVolume, maximumVolume, icon, isBrightness = false)
}
// Handle brightness change
override fun onBrightnessChanged(brightness: Double) {
if (config.shouldLowestValueEnableAutoBrightness && brightness <= 0) {
showFeedbackView(
str("revanced_swipe_lowest_value_enable_auto_brightness_overlay_text"),
autoBrightnessIcon,
)
} else if (brightness >= 0) {
showFeedbackView("${round(brightness).toInt()}%", manualBrightnessIcon)
showFeedbackView("Auto", 0, 100, autoBrightnessIcon, isBrightness = true)
} else {
val brightnessValue = round(brightness).toInt()
val icon = when {
brightnessValue < 25 -> lowBrightnessIcon
brightnessValue < 50 -> mediumBrightnessIcon
brightnessValue < 75 -> highBrightnessIcon
else -> fullBrightnessIcon
}
showFeedbackView("$brightnessValue%", brightnessValue, 100, icon, isBrightness = true)
}
}
// Begin swipe session
override fun onEnterSwipeSession() {
if (config.shouldEnableHapticFeedback) {
@Suppress("DEPRECATION")
@@ -143,3 +150,229 @@ class SwipeControlsOverlayLayout(
}
}
}
/**
* Abstract base class for progress views.
*/
abstract class AbstractProgressView(
context: Context,
protected val overlayBackgroundOpacity: Int,
protected val overlayShowOverlayMinimalStyle: Boolean,
protected val overlayProgressColor: Int,
protected val overlayFillBackgroundPaint: Int,
protected val overlayTextColor: Int,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
// 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 {
this.style = style
this.color = color
this.strokeCap = strokeCap
this.strokeWidth = strokeWidth
}
// Initialize paints
public val backgroundPaint = createPaint(overlayBackgroundOpacity, style = Paint.Style.FILL)
public val progressPaint = createPaint(overlayProgressColor, style = Paint.Style.STROKE, strokeCap = Paint.Cap.ROUND, strokeWidth = 20f)
public val fillBackgroundPaint = createPaint(overlayFillBackgroundPaint, style = Paint.Style.FILL)
public val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = overlayTextColor
textAlign = Paint.Align.CENTER
textSize = 40f // Can adjust based on need
}
protected var progress = 0
protected var maxProgress = 100
protected var displayText: String = "0"
protected var isBrightness = true
public var icon: Drawable? = null
init {
// Stroke widths are now set in createPaint for progressPaint and fillBackgroundPaint
}
fun setProgress(value: Int, max: Int, text: String, isBrightnessMode: Boolean) {
progress = value
maxProgress = max
displayText = text
isBrightness = isBrightnessMode
invalidate()
}
override fun onDraw(canvas: Canvas) {
// Base class implementation can be empty
}
}
/**
* Custom view for rendering a circular progress indicator with icons and text.
*/
class CircularProgressView(
context: Context,
overlayBackgroundOpacity: Int,
overlayShowOverlayMinimalStyle: Boolean,
overlayProgressColor: Int,
overlayFillBackgroundPaint: Int,
overlayTextColor: Int,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractProgressView(
context,
overlayBackgroundOpacity,
overlayShowOverlayMinimalStyle,
overlayProgressColor,
overlayFillBackgroundPaint,
overlayTextColor,
attrs,
defStyleAttr
) {
private val rectF = RectF()
init {
textPaint.textSize = 40f // Override default text size for circular view
progressPaint.strokeWidth = 20f
fillBackgroundPaint.strokeWidth = 20f
progressPaint.strokeCap = Paint.Cap.ROUND
fillBackgroundPaint.strokeCap = Paint.Cap.BUTT
progressPaint.style = Paint.Style.STROKE
fillBackgroundPaint.style = Paint.Style.STROKE
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val size = min(width, height).toFloat()
rectF.set(20f, 20f, size - 20f, size - 20f)
canvas.drawOval(rectF, fillBackgroundPaint) // Draw the outer ring.
canvas.drawCircle(width / 2f, height / 2f, size / 3, backgroundPaint) // Draw the inner circle.
// Select the paint for drawing based on whether it's brightness or volume.
val sweepAngle = (progress.toFloat() / maxProgress) * 360
canvas.drawArc(rectF, -90f, sweepAngle, false, progressPaint) // Draw the progress arc.
// Draw the icon in the center.
icon?.let {
val iconSize = if (overlayShowOverlayMinimalStyle) 100 else 80
val iconX = (width - iconSize) / 2
val iconY = (height / 2) - if (overlayShowOverlayMinimalStyle) 50 else 80
it.setBounds(iconX, iconY, iconX + iconSize, iconY + iconSize)
it.draw(canvas)
}
// If not a minimal style mode, draw the text inside the ring.
if (!overlayShowOverlayMinimalStyle) {
canvas.drawText(displayText, width / 2f, height / 2f + 60f, textPaint)
}
}
}
/**
* Custom view for rendering a rectangular progress bar with icons and text.
*/
class HorizontalProgressView(
context: Context,
overlayBackgroundOpacity: Int,
overlayShowOverlayMinimalStyle: Boolean,
overlayProgressColor: Int,
overlayFillBackgroundPaint: Int,
overlayTextColor: Int,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AbstractProgressView(
context,
overlayBackgroundOpacity,
overlayShowOverlayMinimalStyle,
overlayProgressColor,
overlayFillBackgroundPaint,
overlayTextColor,
attrs,
defStyleAttr
) {
private val iconSize = 60f
private val padding = 40f
init {
textPaint.textSize = 36f // Override default text size for horizontal view
progressPaint.strokeWidth = 0f
progressPaint.strokeCap = Paint.Cap.BUTT
progressPaint.style = Paint.Style.FILL
fillBackgroundPaint.style = Paint.Style.FILL
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val width = width.toFloat()
val height = height.toFloat()
// Radius for rounded corners
val cornerRadius = min(width, height) / 2
// Calculate the total width for the elements
val minimalElementWidth = 5 * padding + iconSize
// Calculate the starting point (X) to center the elements
val minimalStartX = (width - minimalElementWidth) / 2
// Draw the background
if (!overlayShowOverlayMinimalStyle) {
canvas.drawRoundRect(0f, 0f, width, height, cornerRadius, cornerRadius, backgroundPaint)
} else {
canvas.drawRoundRect(minimalStartX, 0f, minimalStartX + minimalElementWidth, height, cornerRadius, cornerRadius, backgroundPaint)
}
if (!overlayShowOverlayMinimalStyle) {
// Draw the fill background
val startX = 2 * padding + iconSize
val endX = width - 4 * padding
val fillWidth = endX - startX
canvas.drawRoundRect(
startX,
height / 2 - 5f,
endX,
height / 2 + 5f,
10f, 10f,
fillBackgroundPaint
)
// Draw the progress
val progressWidth = (progress.toFloat() / maxProgress) * fillWidth
canvas.drawRoundRect(
startX,
height / 2 - 5f,
startX + progressWidth,
height / 2 + 5f,
10f, 10f,
progressPaint
)
}
// Draw the icon
icon?.let {
val iconX = if (!overlayShowOverlayMinimalStyle) {
padding
} else {
padding + minimalStartX
}
val iconY = height / 2 - iconSize / 2
it.setBounds(iconX.toInt(), iconY.toInt(), (iconX + iconSize).toInt(), (iconY + iconSize).toInt())
it.draw(canvas)
}
// Draw the text on the right
val textX = if (!overlayShowOverlayMinimalStyle) {
width - 2 * padding
} else {
minimalStartX + minimalElementWidth - 2 * padding
}
val textY = height / 2 + textPaint.textSize / 3
// Draw the text
canvas.drawText(displayText, textX, textY, textPaint)
}
}

View File

@@ -1,38 +1,35 @@
package app.revanced.extension.youtube.videoplayer;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.patches.CopyVideoUrlPatch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public class CopyVideoUrlButton extends PlayerControlButton {
public class CopyVideoUrlButton {
@Nullable
private static CopyVideoUrlButton instance;
public CopyVideoUrlButton(ViewGroup viewGroup) {
super(
viewGroup,
"revanced_copy_video_url_button",
Settings.COPY_VIDEO_URL,
view -> CopyVideoUrlPatch.copyUrl(false),
view -> {
CopyVideoUrlPatch.copyUrl(true);
return true;
}
);
}
private static PlayerControlButton instance;
/**
* Injection point.
*/
public static void initializeButton(View view) {
public static void initializeButton(View controlsView) {
try {
instance = new CopyVideoUrlButton((ViewGroup) view);
instance = new PlayerControlButton(
controlsView,
"revanced_copy_video_url_button",
"revanced_copy_video_url_button_placeholder",
Settings.COPY_VIDEO_URL::get,
view -> CopyVideoUrlPatch.copyUrl(false),
view -> {
CopyVideoUrlPatch.copyUrl(true);
return true;
}
);
} catch (Exception ex) {
Logger.printException(() -> "initializeButton failure", ex);
}
@@ -41,14 +38,14 @@ public class CopyVideoUrlButton extends PlayerControlButton {
/**
* injection point
*/
public static void changeVisibilityImmediate(boolean visible) {
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) instance.setVisibilityImmediate(visible);
}
/**
* injection point
*/
public static void changeVisibility(boolean visible, boolean animated) {
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) instance.setVisibility(visible, animated);
}
}

View File

@@ -1,38 +1,35 @@
package app.revanced.extension.youtube.videoplayer;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.patches.CopyVideoUrlPatch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused")
public class CopyVideoUrlTimestampButton extends PlayerControlButton {
public class CopyVideoUrlTimestampButton {
@Nullable
private static CopyVideoUrlTimestampButton instance;
public CopyVideoUrlTimestampButton(ViewGroup bottomControlsViewGroup) {
super(
bottomControlsViewGroup,
"revanced_copy_video_url_timestamp_button",
Settings.COPY_VIDEO_URL_TIMESTAMP,
view -> CopyVideoUrlPatch.copyUrl(true),
view -> {
CopyVideoUrlPatch.copyUrl(false);
return true;
}
);
}
private static PlayerControlButton instance;
/**
* Injection point.
*/
public static void initializeButton(View bottomControlsViewGroup) {
public static void initializeButton(View controlsView) {
try {
instance = new CopyVideoUrlTimestampButton((ViewGroup) bottomControlsViewGroup);
instance = new PlayerControlButton(
controlsView,
"revanced_copy_video_url_timestamp_button",
"revanced_copy_video_url_timestamp_button_placeholder",
Settings.COPY_VIDEO_URL_TIMESTAMP::get,
view -> CopyVideoUrlPatch.copyUrl(true),
view -> {
CopyVideoUrlPatch.copyUrl(false);
return true;
}
);
} catch (Exception ex) {
Logger.printException(() -> "initializeButton failure", ex);
}
@@ -41,14 +38,14 @@ public class CopyVideoUrlTimestampButton extends PlayerControlButton {
/**
* injection point
*/
public static void changeVisibilityImmediate(boolean visible) {
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) instance.setVisibilityImmediate(visible);
}
/**
* injection point
*/
public static void changeVisibility(boolean visible, boolean animated) {
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) instance.setVisibility(visible, animated);
}
}

View File

@@ -1,7 +1,6 @@
package app.revanced.extension.youtube.videoplayer;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
@@ -11,26 +10,23 @@ import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused")
public class ExternalDownloadButton extends PlayerControlButton {
public class ExternalDownloadButton {
@Nullable
private static ExternalDownloadButton instance;
public ExternalDownloadButton(ViewGroup viewGroup) {
super(
viewGroup,
"revanced_external_download_button",
Settings.EXTERNAL_DOWNLOADER,
ExternalDownloadButton::onDownloadClick,
null
);
}
private static PlayerControlButton instance;
/**
* Injection point.
*/
public static void initializeButton(View view) {
public static void initializeButton(View controlsView) {
try {
instance = new ExternalDownloadButton((ViewGroup) view);
instance = new PlayerControlButton(
controlsView,
"revanced_external_download_button",
"revanced_external_download_button_placeholder",
Settings.EXTERNAL_DOWNLOADER::get,
ExternalDownloadButton::onDownloadClick,
null
);
} catch (Exception ex) {
Logger.printException(() -> "initializeButton failure", ex);
}
@@ -39,14 +35,14 @@ public class ExternalDownloadButton extends PlayerControlButton {
/**
* injection point
*/
public static void changeVisibilityImmediate(boolean visible) {
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) instance.setVisibilityImmediate(visible);
}
/**
* injection point
* Injection point
*/
public static void changeVisibility(boolean visible, boolean animated) {
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) instance.setVisibility(visible, animated);
}

View File

@@ -1,35 +1,31 @@
package app.revanced.extension.youtube.videoplayer;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger;
@SuppressWarnings("unused")
public class PlaybackSpeedDialogButton extends PlayerControlButton {
public class PlaybackSpeedDialogButton {
@Nullable
private static PlaybackSpeedDialogButton instance;
public PlaybackSpeedDialogButton(ViewGroup viewGroup) {
super(
viewGroup,
"revanced_playback_speed_dialog_button",
Settings.PLAYBACK_SPEED_DIALOG_BUTTON,
view -> CustomPlaybackSpeedPatch.showOldPlaybackSpeedMenu(),
null
);
}
private static PlayerControlButton instance;
/**
* Injection point.
*/
public static void initializeButton(View view) {
public static void initializeButton(View controlsView) {
try {
instance = new PlaybackSpeedDialogButton((ViewGroup) view);
instance = new PlayerControlButton(
controlsView,
"revanced_playback_speed_dialog_button",
"revanced_playback_speed_dialog_button_placeholder",
Settings.PLAYBACK_SPEED_DIALOG_BUTTON::get,
view -> CustomPlaybackSpeedPatch.showOldPlaybackSpeedMenu(),
null
);
} catch (Exception ex) {
Logger.printException(() -> "initializeButton failure", ex);
}
@@ -38,14 +34,14 @@ public class PlaybackSpeedDialogButton extends PlayerControlButton {
/**
* injection point
*/
public static void changeVisibilityImmediate(boolean visible) {
public static void setVisibilityImmediate(boolean visible) {
if (instance != null) instance.setVisibilityImmediate(visible);
}
/**
* injection point
*/
public static void changeVisibility(boolean visible, boolean animated) {
public static void setVisibility(boolean visible, boolean animated) {
if (instance != null) instance.setVisibility(visible, animated);
}
}

View File

@@ -1,73 +1,92 @@
package app.revanced.extension.youtube.videoplayer;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.Objects;
import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.youtube.shared.PlayerType;
import kotlin.Unit;
public abstract class PlayerControlButton {
private static final Animation fadeIn;
private static final Animation fadeOut;
public class PlayerControlButton {
public interface PlayerControlButtonVisibility {
/**
* @return If the button should be shown when the player overlay is visible.
*/
boolean shouldBeShown();
}
private static final int fadeInDuration;
private static final int fadeOutDuration;
private static final Animation fadeInAnimation;
private static final Animation fadeOutAnimation;
private static final Animation fadeOutImmediate;
private final WeakReference<ImageView> buttonRef;
protected final BooleanSetting setting;
protected boolean isVisible;
static {
// TODO: check if these durations are correct.
fadeIn = Utils.getResourceAnimation("fade_in");
fadeIn.setDuration(Utils.getResourceInteger("fade_duration_fast"));
fadeInDuration = Utils.getResourceInteger("fade_duration_fast");
fadeOutDuration = Utils.getResourceInteger("fade_duration_scheduled");
fadeOut = Utils.getResourceAnimation("fade_out");
fadeOut.setDuration(Utils.getResourceInteger("fade_duration_scheduled"));
fadeInAnimation = Utils.getResourceAnimation("fade_in");
fadeInAnimation.setDuration(fadeInDuration);
fadeOutAnimation = Utils.getResourceAnimation("fade_out");
fadeOutAnimation.setDuration(fadeOutDuration);
// Animation for the fast fade out after tapping the overlay.
// Currently not used but should be.
fadeOutImmediate = Utils.getResourceAnimation("abc_fade_out");
fadeOutImmediate.setDuration(Utils.getResourceInteger("fade_duration_fast"));
}
@NonNull
public static Animation getButtonFadeIn() {
return fadeIn;
}
private final WeakReference<View> buttonRef;
/**
* Empty view with the same layout size as the button. Used to fill empty space while the
* fade out animation runs. Without this the chapter titles overlapping the button when fading out.
*/
private final WeakReference<View> placeHolderRef;
private final PlayerControlButtonVisibility visibilityCheck;
private boolean isVisible;
@NonNull
public static Animation getButtonFadeOut() {
return fadeOut;
}
@NonNull
public static Animation getButtonFadeOutImmediately() {
return fadeOutImmediate;
}
public PlayerControlButton(@NonNull ViewGroup bottomControlsViewGroup, @NonNull String imageViewButtonId,
@NonNull BooleanSetting booleanSetting, @NonNull View.OnClickListener onClickListener,
public PlayerControlButton(View controlsViewGroup,
String imageViewButtonId,
@Nullable String placeholderId,
PlayerControlButtonVisibility buttonVisibility,
View.OnClickListener onClickListener,
@Nullable View.OnLongClickListener longClickListener) {
Logger.printDebug(() -> "Initializing button: " + imageViewButtonId);
ImageView imageView = Objects.requireNonNull(bottomControlsViewGroup.findViewById(
Utils.getResourceIdentifier(imageViewButtonId, "id")
));
ImageView imageView = Utils.getChildViewByResourceName(controlsViewGroup, imageViewButtonId);
imageView.setVisibility(View.GONE);
View tempPlaceholder = null;
if (placeholderId != null) {
tempPlaceholder = Utils.getChildViewByResourceName(controlsViewGroup, placeholderId);
tempPlaceholder.setVisibility(View.GONE);
}
placeHolderRef = new WeakReference<>(tempPlaceholder);
imageView.setOnClickListener(onClickListener);
if (longClickListener != null) {
imageView.setOnLongClickListener(longClickListener);
}
setting = booleanSetting;
visibilityCheck = buttonVisibility;
buttonRef = new WeakReference<>(imageView);
isVisible = false;
// Update the visibility after the player type changes.
// This ensures that button animations are cleared and their states are updated correctly
// when switching between states like minimized, maximized, or fullscreen, preventing
// "stuck" animations or incorrect visibility. Without this fix the issue is most noticable
// when maximizing type 3 miniplayer.
PlayerType.getOnChange().addObserver((PlayerType type) -> {
playerTypeChanged(type);
return Unit.INSTANCE;
});
}
public void setVisibilityImmediate(boolean visible) {
@@ -92,26 +111,80 @@ public abstract class PlayerControlButton {
if (isVisible == visible) return;
isVisible = visible;
ImageView iView = buttonRef.get();
if (iView == null) {
return;
}
View button = buttonRef.get();
if (button == null) return;
if (visible && setting.get()) {
iView.clearAnimation();
View placeholder = placeHolderRef.get();
final boolean shouldBeShown = visibilityCheck.shouldBeShown();
if (visible && shouldBeShown) {
button.clearAnimation();
if (animated) {
iView.startAnimation(PlayerControlButton.getButtonFadeIn());
button.startAnimation(PlayerControlButton.fadeInAnimation);
}
iView.setVisibility(View.VISIBLE);
} else if (iView.getVisibility() == View.VISIBLE) {
iView.clearAnimation();
if (animated) {
iView.startAnimation(PlayerControlButton.getButtonFadeOut());
button.setVisibility(View.VISIBLE);
if (placeholder != null) {
placeholder.setVisibility(View.GONE);
}
} else {
if (button.getVisibility() == View.VISIBLE) {
button.clearAnimation();
if (animated) {
button.startAnimation(PlayerControlButton.fadeOutAnimation);
}
button.setVisibility(View.GONE);
}
if (placeholder != null) {
placeholder.setVisibility(shouldBeShown
? View.VISIBLE
: View.GONE);
}
iView.setVisibility(View.GONE);
}
} catch (Exception ex) {
Logger.printException(() -> "setVisibility failure", ex);
Logger.printException(() -> "private_setVisibility failure", ex);
}
}
}
/**
* Synchronizes the button state after the player state changes.
*/
private void playerTypeChanged(PlayerType newType) {
if (newType != PlayerType.WATCH_WHILE_MINIMIZED && !newType.isMaximizedOrFullscreen()) {
return;
}
View button = buttonRef.get();
if (button == null) return;
button.clearAnimation();
View placeholder = placeHolderRef.get();
if (visibilityCheck.shouldBeShown()) {
if (isVisible) {
button.setVisibility(View.VISIBLE);
if (placeholder != null) placeholder.setVisibility(View.GONE);
} else {
button.setVisibility(View.GONE);
if (placeholder != null) placeholder.setVisibility(View.VISIBLE);
}
} else {
button.setVisibility(View.GONE);
if (placeholder != null) placeholder.setVisibility(View.GONE);
}
}
public void hide() {
if (!isVisible) return;
Utils.verifyOnMainThread();
View view = buttonRef.get();
if (view == null) return;
view.setVisibility(View.GONE);
view = placeHolderRef.get();
if (view != null) view.setVisibility(View.GONE);
isVisible = false;
}
}

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