Compare commits
98 Commits
v1.5.1-dev
...
main
Author | SHA1 | Date |
---|---|---|
semantic-release-bot | 1b82e985b6 | |
oSumAtrIX | 9147842ac7 | |
semantic-release-bot | e3994d381b | |
oSumAtrIX | 4c1f82aa22 | |
semantic-release-bot | ea184d050e | |
MarcaD | 6bd0ac20dc | |
semantic-release-bot | 78cf116e58 | |
LisoUseInAIKyrios | 303754c46f | |
semantic-release-bot | 195a521238 | |
LisoUseInAIKyrios | 978233843d | |
LisoUseInAIKyrios | 578a27dea5 | |
semantic-release-bot | 6710acd928 | |
LisoUseInAIKyrios | b74e54481a | |
semantic-release-bot | a14567df27 | |
LisoUseInAIKyrios | 77c9825b82 | |
semantic-release-bot | 44dea1af4e | |
LisoUseInAIKyrios | 81251f9a34 | |
semantic-release-bot | 7d102e7a69 | |
LisoUseInAIKyrios | 9e9d96910c | |
semantic-release-bot | a7ea203fad | |
benjy3gg | 44c3cc4636 | |
semantic-release-bot | 07fe660037 | |
LisoUseInAIKyrios | 75fa5797f7 | |
semantic-release-bot | 0d5c6e87d2 | |
LisoUseInAIKyrios | 656ca17ffc | |
semantic-release-bot | 973e51ea1b | |
LisoUseInAIKyrios | 4164ed3486 | |
semantic-release-bot | 5ed4292e2e | |
oSumAtrIX | 9de566ca02 | |
semantic-release-bot | a9da29926a | |
LisoUseInAIKyrios | 6e9e12235a | |
oSumAtrIX | e86d73c8af | |
semantic-release-bot | 89c534e95d | |
LisoUseInAIKyrios | a78c3ff09e | |
LisoUseInAIKyrios | b2b6b8c3d7 | |
semantic-release-bot | 4e64b690ab | |
oSumAtrIX | d03edb2750 | |
semantic-release-bot | d06b94cbe6 | |
MarcaD | 1c8e2b2941 | |
semantic-release-bot | efd03012d0 | |
LisoUseInAIKyrios | c3bfa77d62 | |
semantic-release-bot | 3ff5dcd05f | |
LisoUseInAIKyrios | 786ac9d2b7 | |
semantic-release-bot | bcc9126107 | |
LisoUseInAIKyrios | 3ce100ced5 | |
semantic-release-bot | 4cacbf6f2c | |
LisoUseInAIKyrios | ba308690cf | |
semantic-release-bot | 7cd0c6c3c1 | |
LisoUseInAIKyrios | b8f260ebd3 | |
semantic-release-bot | f5720f71a6 | |
LisoUseInAIKyrios | 9938bbf0de | |
semantic-release-bot | d60dcf98ab | |
LisoUseInAIKyrios | bba421ddb6 | |
semantic-release-bot | 8e36a018f4 | |
LisoUseInAIKyrios | 13dc17288d | |
semantic-release-bot | 92c591735a | |
LisoUseInAIKyrios | 9108205445 | |
semantic-release-bot | e9cd898651 | |
LisoUseInAIKyrios | 0586fb70e3 | |
semantic-release-bot | 79a3a44961 | |
LisoUseInAIKyrios | a2b15433cf | |
semantic-release-bot | 96f42b3892 | |
oSumAtrIX | d6cd550880 | |
semantic-release-bot | 5b4fd770de | |
LisoUseInAIKyrios | acfa3c9886 | |
semantic-release-bot | 4bd805e54d | |
LisoUseInAIKyrios | c132670400 | |
semantic-release-bot | 4fd3cc906d | |
LisoUseInAIKyrios | eeaeb49f2a | |
semantic-release-bot | dba707800e | |
LisoUseInAIKyrios | ffc3437843 | |
semantic-release-bot | 4939a22da1 | |
LisoUseInAIKyrios | f2e15a2e1f | |
LisoUseInAIKyrios | eba73c5947 | |
semantic-release-bot | c0764e9046 | |
LisoUseInAIKyrios | c5d38a7e07 | |
LisoUseInAIKyrios | f6de32884e | |
semantic-release-bot | 628c25e808 | |
LisoUseInAIKyrios | c5c9de500d | |
nullptr | fedace02fd | |
semantic-release-bot | bddcc97392 | |
nullptr | c420891e3e | |
semantic-release-bot | 33da670984 | |
LisoUseInAIKyrios | b2fe105199 | |
semantic-release-bot | 20162f977b | |
LisoUseInAIKyrios | cbccb46e63 | |
semantic-release-bot | eaa2e1139c | |
LisoUseInAIKyrios | 38ae5aac84 | |
semantic-release-bot | 530ba18364 | |
oSumAtrIX | 6c6c636a43 | |
semantic-release-bot | 22e2163753 | |
oSumAtrIX | fd2a9d0287 | |
semantic-release-bot | f11d291c3d | |
LisoUseInAIKyrios | 4c81e96a74 | |
semantic-release-bot | ae42f27196 | |
oSumAtrIX | 9761a110c4 | |
semantic-release-bot | e502bad344 | |
oSumAtrIX | 999727d5e6 |
388
CHANGELOG.md
388
CHANGELOG.md
|
@ -1,3 +1,391 @@
|
|||
# [1.9.0](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0...v1.9.0) (2024-05-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use Java instead of Kotlin Regex to improve reliability ([#628](https://github.com/ReVanced/revanced-integrations/issues/628)) ([44c3cc4](https://github.com/ReVanced/revanced-integrations/commit/44c3cc46367520f780537716236e0bed3b9537e6))
|
||||
* **YouTube - Client spoof:** Spoof client to fix playback ([#637](https://github.com/ReVanced/revanced-integrations/issues/637)) ([4c1f82a](https://github.com/ReVanced/revanced-integrations/commit/4c1f82aa228239b041c7d3657f1117abd7516991))
|
||||
* **YouTube - Hide keyword content:** Filter Shorts found in horizontal shelves ([75fa579](https://github.com/ReVanced/revanced-integrations/commit/75fa5797f70123f68d4676201503cf35dcef46dc))
|
||||
* **YouTube - Hide Shorts components:** Hide old layout like/dislike buttons without leaving empty space ([9782338](https://github.com/ReVanced/revanced-integrations/commit/978233843d2dcf9a8a0ddf9c9517d25c973e8689))
|
||||
* **YouTube - Hide Shorts components:** Hide Shorts in search result horizontal shelves ([#625](https://github.com/ReVanced/revanced-integrations/issues/625)) ([656ca17](https://github.com/ReVanced/revanced-integrations/commit/656ca17ffc67cae7012436e5cddd7112b73fc450))
|
||||
* **YouTube - Hide video action buttons:** Remove obsolete `hide Shop button` ([#618](https://github.com/ReVanced/revanced-integrations/issues/618)) ([a78c3ff](https://github.com/ReVanced/revanced-integrations/commit/a78c3ff09e81b1d1e0fe863135ad19e699b702c8))
|
||||
* **YouTube - Hide video action buttons:** Updated path filter ([b74e544](https://github.com/ReVanced/revanced-integrations/commit/b74e54481ad4040d7742bb4f9a6a20351bb9ef71))
|
||||
* **YouTube - Player flyout menu:** Remove obsolete `Hide report menu` ([9e9d969](https://github.com/ReVanced/revanced-integrations/commit/9e9d96910c27568013f2a0744687eb8fba685175))
|
||||
* **YouTube - Restore old video quality menu:** Do not make click sounds when opening Shorts quality menu ([578a27d](https://github.com/ReVanced/revanced-integrations/commit/578a27dea546f572c3fd270d001fd6024f59dd1a))
|
||||
* **YouTube - Restore old video quality menu:** Show advanced quality menu in Shorts quality flyout ([#632](https://github.com/ReVanced/revanced-integrations/issues/632)) ([77c9825](https://github.com/ReVanced/revanced-integrations/commit/77c9825b824f95ff6e2c2d5776f4c1d231e6fd0c))
|
||||
* **YouTube - Settings:** Use same background color for about screen if Theme patch is not included ([4164ed3](https://github.com/ReVanced/revanced-integrations/commit/4164ed3486330add8ff158f5b6765fa26587154d))
|
||||
* **YouTube - SponsorBlock:** Show correct segment times if video is over 24 hours in length ([#630](https://github.com/ReVanced/revanced-integrations/issues/630)) ([81251f9](https://github.com/ReVanced/revanced-integrations/commit/81251f9a34ef9f252ce69b01b40e29480a5add9f))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Comments:** Add option to hide timestamp and emoji buttons ([#621](https://github.com/ReVanced/revanced-integrations/issues/621)) ([6e9e122](https://github.com/ReVanced/revanced-integrations/commit/6e9e12235a69a1f3759180abd48e65b151b62e9a))
|
||||
* **YouTube - Hide ads:** Add option to hide the 'Visit store' button on channel pages ([#622](https://github.com/ReVanced/revanced-integrations/issues/622)) ([9de566c](https://github.com/ReVanced/revanced-integrations/commit/9de566ca02398ab9c628565091ed02316c5e1ed0))
|
||||
* **YouTube - Hide Shorts components:** Hide 'Buy super thanks' button ([#633](https://github.com/ReVanced/revanced-integrations/issues/633)) ([303754c](https://github.com/ReVanced/revanced-integrations/commit/303754c46f6d3471405d67b7911065fc3a87f721))
|
||||
* **YouTube - Hide Shorts components:** Hide like / dislike button in video ads ([#619](https://github.com/ReVanced/revanced-integrations/issues/619)) ([b2b6b8c](https://github.com/ReVanced/revanced-integrations/commit/b2b6b8c3d7d3c706552ffe70f3ed0314fe5284b6))
|
||||
* **YouTube - Navigation buttons:** Add option to hide navigation button labels ([#635](https://github.com/ReVanced/revanced-integrations/issues/635)) ([6bd0ac2](https://github.com/ReVanced/revanced-integrations/commit/6bd0ac20dcfc76bfd044fb9f2f03ce98d6efc535))
|
||||
|
||||
# [1.9.0-dev.15](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.14...v1.9.0-dev.15) (2024-05-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Client spoof:** Spoof client to fix playback ([#637](https://github.com/ReVanced/revanced-integrations/issues/637)) ([4c1f82a](https://github.com/ReVanced/revanced-integrations/commit/4c1f82aa228239b041c7d3657f1117abd7516991))
|
||||
|
||||
# [1.9.0-dev.14](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.13...v1.9.0-dev.14) (2024-05-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Navigation buttons:** Add option to hide navigation button labels ([#635](https://github.com/ReVanced/revanced-integrations/issues/635)) ([6bd0ac2](https://github.com/ReVanced/revanced-integrations/commit/6bd0ac20dcfc76bfd044fb9f2f03ce98d6efc535))
|
||||
|
||||
# [1.9.0-dev.13](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.12...v1.9.0-dev.13) (2024-05-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide 'Buy super thanks' button ([#633](https://github.com/ReVanced/revanced-integrations/issues/633)) ([303754c](https://github.com/ReVanced/revanced-integrations/commit/303754c46f6d3471405d67b7911065fc3a87f721))
|
||||
|
||||
# [1.9.0-dev.12](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.11...v1.9.0-dev.12) (2024-05-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide old layout like/dislike buttons without leaving empty space ([9782338](https://github.com/ReVanced/revanced-integrations/commit/978233843d2dcf9a8a0ddf9c9517d25c973e8689))
|
||||
* **YouTube - Restore old video quality menu:** Do not make click sounds when opening Shorts quality menu ([578a27d](https://github.com/ReVanced/revanced-integrations/commit/578a27dea546f572c3fd270d001fd6024f59dd1a))
|
||||
|
||||
# [1.9.0-dev.11](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.10...v1.9.0-dev.11) (2024-05-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide video action buttons:** Updated path filter ([b74e544](https://github.com/ReVanced/revanced-integrations/commit/b74e54481ad4040d7742bb4f9a6a20351bb9ef71))
|
||||
|
||||
# [1.9.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.9...v1.9.0-dev.10) (2024-05-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Restore old video quality menu:** Show advanced quality menu in Shorts quality flyout ([#632](https://github.com/ReVanced/revanced-integrations/issues/632)) ([77c9825](https://github.com/ReVanced/revanced-integrations/commit/77c9825b824f95ff6e2c2d5776f4c1d231e6fd0c))
|
||||
|
||||
# [1.9.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.8...v1.9.0-dev.9) (2024-05-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - SponsorBlock:** Show correct segment times if video is over 24 hours in length ([#630](https://github.com/ReVanced/revanced-integrations/issues/630)) ([81251f9](https://github.com/ReVanced/revanced-integrations/commit/81251f9a34ef9f252ce69b01b40e29480a5add9f))
|
||||
|
||||
# [1.9.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.7...v1.9.0-dev.8) (2024-05-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Player flyout menu:** Remove obsolete `Hide report menu` ([9e9d969](https://github.com/ReVanced/revanced-integrations/commit/9e9d96910c27568013f2a0744687eb8fba685175))
|
||||
|
||||
# [1.9.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.6...v1.9.0-dev.7) (2024-05-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use Java instead of Kotlin Regex to improve reliability ([#628](https://github.com/ReVanced/revanced-integrations/issues/628)) ([44c3cc4](https://github.com/ReVanced/revanced-integrations/commit/44c3cc46367520f780537716236e0bed3b9537e6))
|
||||
|
||||
# [1.9.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.5...v1.9.0-dev.6) (2024-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide keyword content:** Filter Shorts found in horizontal shelves ([75fa579](https://github.com/ReVanced/revanced-integrations/commit/75fa5797f70123f68d4676201503cf35dcef46dc))
|
||||
|
||||
# [1.9.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.4...v1.9.0-dev.5) (2024-04-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide Shorts in search result horizontal shelves ([#625](https://github.com/ReVanced/revanced-integrations/issues/625)) ([656ca17](https://github.com/ReVanced/revanced-integrations/commit/656ca17ffc67cae7012436e5cddd7112b73fc450))
|
||||
|
||||
# [1.9.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.3...v1.9.0-dev.4) (2024-04-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Settings:** Use same background color for about screen if Theme patch is not included ([4164ed3](https://github.com/ReVanced/revanced-integrations/commit/4164ed3486330add8ff158f5b6765fa26587154d))
|
||||
|
||||
# [1.9.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.2...v1.9.0-dev.3) (2024-04-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide ads:** Add option to hide the 'Visit store' button on channel pages ([#622](https://github.com/ReVanced/revanced-integrations/issues/622)) ([9de566c](https://github.com/ReVanced/revanced-integrations/commit/9de566ca02398ab9c628565091ed02316c5e1ed0))
|
||||
|
||||
# [1.9.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.9.0-dev.1...v1.9.0-dev.2) (2024-04-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Comments:** Add option to hide timestamp and emoji buttons ([#621](https://github.com/ReVanced/revanced-integrations/issues/621)) ([6e9e122](https://github.com/ReVanced/revanced-integrations/commit/6e9e12235a69a1f3759180abd48e65b151b62e9a))
|
||||
|
||||
# [1.9.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0...v1.9.0-dev.1) (2024-04-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide video action buttons:** Remove obsolete `hide Shop button` ([#618](https://github.com/ReVanced/revanced-integrations/issues/618)) ([a78c3ff](https://github.com/ReVanced/revanced-integrations/commit/a78c3ff09e81b1d1e0fe863135ad19e699b702c8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide like / dislike button in video ads ([#619](https://github.com/ReVanced/revanced-integrations/issues/619)) ([b2b6b8c](https://github.com/ReVanced/revanced-integrations/commit/b2b6b8c3d7d3c706552ffe70f3ed0314fe5284b6))
|
||||
|
||||
# [1.8.0](https://github.com/ReVanced/revanced-integrations/compare/v1.7.0...v1.8.0) (2024-04-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use correct hide playables setting key ([a2b1543](https://github.com/ReVanced/revanced-integrations/commit/a2b15433cffec082394a50d14f7eef625a6351c1))
|
||||
* **YouTube - Disable suggested video end screen:** Require app restart ([38ae5aa](https://github.com/ReVanced/revanced-integrations/commit/38ae5aac845745824218a08053db519a3325d7a9))
|
||||
* **YouTube - GmsCore support:** Prompt to disable battery optimizations, if not done already ([#601](https://github.com/ReVanced/revanced-integrations/issues/601)) ([c5c9de5](https://github.com/ReVanced/revanced-integrations/commit/c5c9de500d8f1268799e55c31c446bfe8336f79a))
|
||||
* **YouTube - Hide ads:** rename `Hide paid content` to `Hide paid promotion label` ([#616](https://github.com/ReVanced/revanced-integrations/issues/616)) ([13dc172](https://github.com/ReVanced/revanced-integrations/commit/13dc17288d13d024a3fbe318ee0fb23a0d46af85))
|
||||
* **YouTube - Hide keyword content:** Correctly hide content in the subscription tab ([c3bfa77](https://github.com/ReVanced/revanced-integrations/commit/c3bfa77d62b15dedfed8f697583f2f0805f0c2c1))
|
||||
* **YouTube - Hide layout components:** Do not hide playlist shelf in library ([c5d38a7](https://github.com/ReVanced/revanced-integrations/commit/c5d38a7e0791ebb8fe59397fff959cc94e0a7aed))
|
||||
* **YouTube - Hide layout components:** Hide horizontal tile shelves ([ba30869](https://github.com/ReVanced/revanced-integrations/commit/ba308690cf83067d3ddd54622eebcbd14bc15ac8))
|
||||
* **YouTube - Hide load more button:** Include patch with `Hide layout components`, and hide button only in search feed ([#600](https://github.com/ReVanced/revanced-integrations/issues/600)) ([c420891](https://github.com/ReVanced/revanced-integrations/commit/c420891e3ef134f30af79cf2f30da3fa2fe5a455))
|
||||
* **YouTube - Hide Shorts components:** Correctly hide Shorts if navigation tab is changed using device back button ([#611](https://github.com/ReVanced/revanced-integrations/issues/611)) ([ffc3437](https://github.com/ReVanced/revanced-integrations/commit/ffc3437843c24af255d2a0dda9930d2843cac4b6))
|
||||
* **YouTube - Hide Shorts components:** Do not show Shorts suggestions in video player, if all hide Shorts options are enabled ([#613](https://github.com/ReVanced/revanced-integrations/issues/613)) ([c132670](https://github.com/ReVanced/revanced-integrations/commit/c132670400e6bdf17c46b8d04d579fb49c3d2749))
|
||||
* **YouTube - Hide Shorts components:** Hide paid promotion label ([3ce100c](https://github.com/ReVanced/revanced-integrations/commit/3ce100ced57d7099c2209d9a955484f1e7d418e0))
|
||||
* **YouTube - Hide Shorts components:** Hide subscribe button in channel bar ([9938bbf](https://github.com/ReVanced/revanced-integrations/commit/9938bbf0de9592db015ae0cfea83e855e12f0c7e))
|
||||
* **YouTube - Hide Shorts components:** Hide suggested actions in incognito mode ([bba421d](https://github.com/ReVanced/revanced-integrations/commit/bba421ddb63597bf918ecccacfd4a33493016b9f))
|
||||
* **YouTube - Navigation bar hook:** Handle if search is active but hidden behind a maximized player ([cbccb46](https://github.com/ReVanced/revanced-integrations/commit/cbccb46e639003adbed941f9b88c41b4c9998729))
|
||||
* **YouTube - Player flyout menu:** Add hide Lock screen menu ([#609](https://github.com/ReVanced/revanced-integrations/issues/609)) ([b2fe105](https://github.com/ReVanced/revanced-integrations/commit/b2fe105199d4a5958676cbc8f9c701541e8ff24a))
|
||||
* **YouTube - Return YouTube Dislike:** Do not clip compact text when not using English ([eeaeb49](https://github.com/ReVanced/revanced-integrations/commit/eeaeb49f2a562d2690dae184153c303a5b1c4368))
|
||||
* **YouTube - Return YouTube Dislike:** Do not show error toast if API success response contains new lines ([#612](https://github.com/ReVanced/revanced-integrations/issues/612)) ([9108205](https://github.com/ReVanced/revanced-integrations/commit/9108205445c533550db454731d4f9460a3241a03))
|
||||
* **YouTube - Settings:** Do not show a toast if migrating old unknown settings ([f2e15a2](https://github.com/ReVanced/revanced-integrations/commit/f2e15a2e1ff59ae7780cfbd366e5165f4e2b191d))
|
||||
* **YouTube - Spoof device dimensions:** Warn about potential performance issues ([#617](https://github.com/ReVanced/revanced-integrations/issues/617)) ([786ac9d](https://github.com/ReVanced/revanced-integrations/commit/786ac9d2b71886964454fcb748e656d1beed1964))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide layout components:** Add option to hide horizontal shelves ([#598](https://github.com/ReVanced/revanced-integrations/issues/598)) ([fedace0](https://github.com/ReVanced/revanced-integrations/commit/fedace02fd5c443ef37dcf77253438b041f4c3f9))
|
||||
* **YouTube - Hide layout components:** Hide playables ([d6cd550](https://github.com/ReVanced/revanced-integrations/commit/d6cd550880596de5cd2eb4a0d1325a73326d4af9))
|
||||
* **YouTube - Hide Shorts components:** Hide `Shop`, `Location` and `Save sound to playlist` buttons ([#614](https://github.com/ReVanced/revanced-integrations/issues/614)) ([acfa3c9](https://github.com/ReVanced/revanced-integrations/commit/acfa3c98868b6d84572ee682ad806a0282ac6dad))
|
||||
* **YouTube - Hide Shorts components:** Hide tagged products, hide search suggestions ([#615](https://github.com/ReVanced/revanced-integrations/issues/615)) ([0586fb7](https://github.com/ReVanced/revanced-integrations/commit/0586fb70e347c25742e03102441cfb37315b5937))
|
||||
* **YouTube - Swipe controls:** Save and restore brightness and add auto-brightness toggle ([#610](https://github.com/ReVanced/revanced-integrations/issues/610)) ([1c8e2b2](https://github.com/ReVanced/revanced-integrations/commit/1c8e2b29410048a352cb6aad3dd02773459f91a0))
|
||||
* **YouTube:** Add 'About' preference to settings menu ([#608](https://github.com/ReVanced/revanced-integrations/issues/608)) ([b8f260e](https://github.com/ReVanced/revanced-integrations/commit/b8f260ebd3e7c2dc50a57cd060b76f2e0fc4a89c))
|
||||
|
||||
# [1.8.0-dev.20](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.19...v1.8.0-dev.20) (2024-04-21)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Swipe controls:** Save and restore brightness and add auto-brightness toggle ([#610](https://github.com/ReVanced/revanced-integrations/issues/610)) ([1c8e2b2](https://github.com/ReVanced/revanced-integrations/commit/1c8e2b29410048a352cb6aad3dd02773459f91a0))
|
||||
|
||||
# [1.8.0-dev.19](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.18...v1.8.0-dev.19) (2024-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide keyword content:** Correctly hide content in the subscription tab ([c3bfa77](https://github.com/ReVanced/revanced-integrations/commit/c3bfa77d62b15dedfed8f697583f2f0805f0c2c1))
|
||||
|
||||
# [1.8.0-dev.18](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.17...v1.8.0-dev.18) (2024-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Spoof device dimensions:** Warn about potential performance issues ([#617](https://github.com/ReVanced/revanced-integrations/issues/617)) ([786ac9d](https://github.com/ReVanced/revanced-integrations/commit/786ac9d2b71886964454fcb748e656d1beed1964))
|
||||
|
||||
# [1.8.0-dev.17](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.16...v1.8.0-dev.17) (2024-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide paid promotion label ([3ce100c](https://github.com/ReVanced/revanced-integrations/commit/3ce100ced57d7099c2209d9a955484f1e7d418e0))
|
||||
|
||||
# [1.8.0-dev.16](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.15...v1.8.0-dev.16) (2024-04-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Hide horizontal tile shelves ([ba30869](https://github.com/ReVanced/revanced-integrations/commit/ba308690cf83067d3ddd54622eebcbd14bc15ac8))
|
||||
|
||||
# [1.8.0-dev.15](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.14...v1.8.0-dev.15) (2024-04-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube:** Add 'About' preference to settings menu ([#608](https://github.com/ReVanced/revanced-integrations/issues/608)) ([b8f260e](https://github.com/ReVanced/revanced-integrations/commit/b8f260ebd3e7c2dc50a57cd060b76f2e0fc4a89c))
|
||||
|
||||
# [1.8.0-dev.14](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.13...v1.8.0-dev.14) (2024-04-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide subscribe button in channel bar ([9938bbf](https://github.com/ReVanced/revanced-integrations/commit/9938bbf0de9592db015ae0cfea83e855e12f0c7e))
|
||||
|
||||
# [1.8.0-dev.13](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.12...v1.8.0-dev.13) (2024-04-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide suggested actions in incognito mode ([bba421d](https://github.com/ReVanced/revanced-integrations/commit/bba421ddb63597bf918ecccacfd4a33493016b9f))
|
||||
|
||||
# [1.8.0-dev.12](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.11...v1.8.0-dev.12) (2024-04-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide ads:** rename `Hide paid content` to `Hide paid promotion label` ([#616](https://github.com/ReVanced/revanced-integrations/issues/616)) ([13dc172](https://github.com/ReVanced/revanced-integrations/commit/13dc17288d13d024a3fbe318ee0fb23a0d46af85))
|
||||
|
||||
# [1.8.0-dev.11](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.10...v1.8.0-dev.11) (2024-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Return YouTube Dislike:** Do not show error toast if API success response contains new lines ([#612](https://github.com/ReVanced/revanced-integrations/issues/612)) ([9108205](https://github.com/ReVanced/revanced-integrations/commit/9108205445c533550db454731d4f9460a3241a03))
|
||||
|
||||
# [1.8.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.9...v1.8.0-dev.10) (2024-04-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide tagged products, hide search suggestions ([#615](https://github.com/ReVanced/revanced-integrations/issues/615)) ([0586fb7](https://github.com/ReVanced/revanced-integrations/commit/0586fb70e347c25742e03102441cfb37315b5937))
|
||||
|
||||
# [1.8.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.8...v1.8.0-dev.9) (2024-04-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Use correct hide playables setting key ([a2b1543](https://github.com/ReVanced/revanced-integrations/commit/a2b15433cffec082394a50d14f7eef625a6351c1))
|
||||
|
||||
# [1.8.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.7...v1.8.0-dev.8) (2024-04-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide layout components:** Hide playables ([d6cd550](https://github.com/ReVanced/revanced-integrations/commit/d6cd550880596de5cd2eb4a0d1325a73326d4af9))
|
||||
|
||||
# [1.8.0-dev.7](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.6...v1.8.0-dev.7) (2024-04-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Hide `Shop`, `Location` and `Save sound to playlist` buttons ([#614](https://github.com/ReVanced/revanced-integrations/issues/614)) ([acfa3c9](https://github.com/ReVanced/revanced-integrations/commit/acfa3c98868b6d84572ee682ad806a0282ac6dad))
|
||||
|
||||
# [1.8.0-dev.6](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.5...v1.8.0-dev.6) (2024-04-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide Shorts components:** Do not show Shorts suggestions in video player, if all hide Shorts options are enabled ([#613](https://github.com/ReVanced/revanced-integrations/issues/613)) ([c132670](https://github.com/ReVanced/revanced-integrations/commit/c132670400e6bdf17c46b8d04d579fb49c3d2749))
|
||||
|
||||
# [1.8.0-dev.5](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.4...v1.8.0-dev.5) (2024-04-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Return YouTube Dislike:** Do not clip compact text when not using English ([eeaeb49](https://github.com/ReVanced/revanced-integrations/commit/eeaeb49f2a562d2690dae184153c303a5b1c4368))
|
||||
|
||||
# [1.8.0-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.3...v1.8.0-dev.4) (2024-04-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide Shorts components:** Correctly hide Shorts if navigation tab is changed using device back button ([#611](https://github.com/ReVanced/revanced-integrations/issues/611)) ([ffc3437](https://github.com/ReVanced/revanced-integrations/commit/ffc3437843c24af255d2a0dda9930d2843cac4b6))
|
||||
|
||||
# [1.8.0-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.2...v1.8.0-dev.3) (2024-04-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Settings:** Do not show a toast if migrating old unknown settings ([f2e15a2](https://github.com/ReVanced/revanced-integrations/commit/f2e15a2e1ff59ae7780cfbd366e5165f4e2b191d))
|
||||
|
||||
# [1.8.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.8.0-dev.1...v1.8.0-dev.2) (2024-04-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Do not hide playlist shelf in library ([c5d38a7](https://github.com/ReVanced/revanced-integrations/commit/c5d38a7e0791ebb8fe59397fff959cc94e0a7aed))
|
||||
|
||||
# [1.8.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.7.1-dev.4...v1.8.0-dev.1) (2024-04-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - GmsCore support:** Prompt to disable battery optimizations, if not done already ([#601](https://github.com/ReVanced/revanced-integrations/issues/601)) ([c5c9de5](https://github.com/ReVanced/revanced-integrations/commit/c5c9de500d8f1268799e55c31c446bfe8336f79a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide layout components:** Add option to hide horizontal shelves ([#598](https://github.com/ReVanced/revanced-integrations/issues/598)) ([fedace0](https://github.com/ReVanced/revanced-integrations/commit/fedace02fd5c443ef37dcf77253438b041f4c3f9))
|
||||
|
||||
## [1.7.1-dev.4](https://github.com/ReVanced/revanced-integrations/compare/v1.7.1-dev.3...v1.7.1-dev.4) (2024-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide load more button:** Include patch with `Hide layout components`, and hide button only in search feed ([#600](https://github.com/ReVanced/revanced-integrations/issues/600)) ([c420891](https://github.com/ReVanced/revanced-integrations/commit/c420891e3ef134f30af79cf2f30da3fa2fe5a455))
|
||||
|
||||
## [1.7.1-dev.3](https://github.com/ReVanced/revanced-integrations/compare/v1.7.1-dev.2...v1.7.1-dev.3) (2024-04-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Player flyout menu:** Add hide Lock screen menu ([#609](https://github.com/ReVanced/revanced-integrations/issues/609)) ([b2fe105](https://github.com/ReVanced/revanced-integrations/commit/b2fe105199d4a5958676cbc8f9c701541e8ff24a))
|
||||
|
||||
## [1.7.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.7.1-dev.1...v1.7.1-dev.2) (2024-04-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Navigation bar hook:** Handle if search is active but hidden behind a maximized player ([cbccb46](https://github.com/ReVanced/revanced-integrations/commit/cbccb46e639003adbed941f9b88c41b4c9998729))
|
||||
|
||||
## [1.7.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.7.0...v1.7.1-dev.1) (2024-04-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Disable suggested video end screen:** Require app restart ([38ae5aa](https://github.com/ReVanced/revanced-integrations/commit/38ae5aac845745824218a08053db519a3325d7a9))
|
||||
|
||||
# [1.7.0](https://github.com/ReVanced/revanced-integrations/compare/v1.6.0...v1.7.0) (2024-03-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Alternative thumbnails:** Selectively enable for home / subscription / search ([#593](https://github.com/ReVanced/revanced-integrations/issues/593)) ([4c81e96](https://github.com/ReVanced/revanced-integrations/commit/4c81e96a74cfc49923238c4a294b59f36b5e6c36))
|
||||
* **YouTube - GmsCore:** Require ignoring battery optimizations ([#599](https://github.com/ReVanced/revanced-integrations/issues/599)) ([fd2a9d0](https://github.com/ReVanced/revanced-integrations/commit/fd2a9d0287599aaafa817987fd0815e4f0ae72b9))
|
||||
|
||||
# [1.7.0-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.7.0-dev.1...v1.7.0-dev.2) (2024-03-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - GmsCore:** Require ignoring battery optimizations ([#599](https://github.com/ReVanced/revanced-integrations/issues/599)) ([fd2a9d0](https://github.com/ReVanced/revanced-integrations/commit/fd2a9d0287599aaafa817987fd0815e4f0ae72b9))
|
||||
|
||||
# [1.7.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.6.0...v1.7.0-dev.1) (2024-03-29)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Alternative thumbnails:** Selectively enable for home / subscription / search ([#593](https://github.com/ReVanced/revanced-integrations/issues/593)) ([4c81e96](https://github.com/ReVanced/revanced-integrations/commit/4c81e96a74cfc49923238c4a294b59f36b5e6c36))
|
||||
|
||||
# [1.6.0](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0...v1.6.0) (2024-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Correctly hide community posts ([fbc8066](https://github.com/ReVanced/revanced-integrations/commit/fbc80667735778877f262763d6d53afb1dba3bf6))
|
||||
* **YouTube - Navigation buttons:** Hide subscriptions tab ([623d11b](https://github.com/ReVanced/revanced-integrations/commit/623d11b8e8b9f5166b53d8ae195a5d5a6c644661))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **GmsCore support:** Open download link if possible ([#596](https://github.com/ReVanced/revanced-integrations/issues/596)) ([999727d](https://github.com/ReVanced/revanced-integrations/commit/999727d5e6889b6242473f1b14bf618918417e77))
|
||||
|
||||
# [1.6.0-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.5.1-dev.2...v1.6.0-dev.1) (2024-03-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **GmsCore support:** Open download link if possible ([#596](https://github.com/ReVanced/revanced-integrations/issues/596)) ([999727d](https://github.com/ReVanced/revanced-integrations/commit/999727d5e6889b6242473f1b14bf618918417e77))
|
||||
|
||||
## [1.5.1-dev.2](https://github.com/ReVanced/revanced-integrations/compare/v1.5.1-dev.1...v1.5.1-dev.2) (2024-03-28)
|
||||
|
||||
|
||||
|
|
|
@ -1,73 +1,145 @@
|
|||
package app.revanced.integrations.shared;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
public class GmsCoreSupport {
|
||||
private static final String GMS_CORE_PACKAGE_NAME
|
||||
= getGmsCoreVendor() + ".android.gms";
|
||||
= getGmsCoreVendorGroupId() + ".android.gms";
|
||||
private static final Uri GMS_CORE_PROVIDER
|
||||
= Uri.parse("content://" + getGmsCoreVendorGroupId() + ".android.gsf.gservices/prefix");
|
||||
private static final String DONT_KILL_MY_APP_LINK
|
||||
= "https://dontkillmyapp.com";
|
||||
private static final Uri GMS_CORE_PROVIDER
|
||||
= Uri.parse("content://" + getGmsCoreVendor() + ".android.gsf.gservices/prefix");
|
||||
|
||||
private static void search(Context context, String uriString, String message) {
|
||||
Utils.showToastLong(message);
|
||||
|
||||
var intent = new Intent(Intent.ACTION_WEB_SEARCH);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(SearchManager.QUERY, uriString);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public static void checkAvailability() {
|
||||
var context = Objects.requireNonNull(Utils.getContext());
|
||||
|
||||
private static void open(String queryOrLink) {
|
||||
Intent intent;
|
||||
try {
|
||||
context.getPackageManager().getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
||||
} catch (PackageManager.NameNotFoundException exception) {
|
||||
Logger.printInfo(() -> "GmsCore was not found", exception);
|
||||
search(context, getGmsCoreDownloadLink(), str("gms_core_not_installed_warning"));
|
||||
// Check if queryOrLink is a valid URL.
|
||||
new URL(queryOrLink);
|
||||
|
||||
System.exit(0);
|
||||
intent = new Intent(Intent.ACTION_VIEW, Uri.parse(queryOrLink));
|
||||
} catch (MalformedURLException e) {
|
||||
intent = new Intent(Intent.ACTION_WEB_SEARCH);
|
||||
intent.putExtra(SearchManager.QUERY, queryOrLink);
|
||||
}
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
Utils.getContext().startActivity(intent);
|
||||
|
||||
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
||||
if (client != null) return;
|
||||
// Gracefully exit, otherwise the broken app will continue to run.
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||
search(context, DONT_KILL_MY_APP_LINK, str("gms_core_not_running_warning"));
|
||||
private static void showBatteryOptimizationDialog(Activity context,
|
||||
String dialogMessageRef,
|
||||
String positiveButtonStringRef,
|
||||
DialogInterface.OnClickListener onPositiveClickListener) {
|
||||
// Use a delay to allow the activity to finish initializing.
|
||||
// Otherwise, if device is in dark mode the dialog is shown with wrong color scheme.
|
||||
Utils.runOnMainThreadDelayed(() -> {
|
||||
new AlertDialog.Builder(context)
|
||||
.setIconAttribute(android.R.attr.alertDialogIcon)
|
||||
.setTitle(str("gms_core_dialog_title"))
|
||||
.setMessage(str(dialogMessageRef))
|
||||
.setPositiveButton(str(positiveButtonStringRef), onPositiveClickListener)
|
||||
// Allow using back button to skip the action, just in case the check can never be satisfied.
|
||||
.setCancelable(true)
|
||||
.show();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
System.exit(0);
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public static void checkGmsCore(Activity context) {
|
||||
try {
|
||||
// Verify GmsCore is installed.
|
||||
try {
|
||||
PackageManager manager = context.getPackageManager();
|
||||
manager.getPackageInfo(GMS_CORE_PACKAGE_NAME, PackageManager.GET_ACTIVITIES);
|
||||
} catch (PackageManager.NameNotFoundException exception) {
|
||||
Logger.printInfo(() -> "GmsCore was not found");
|
||||
// Cannot show a dialog and must show a toast,
|
||||
// because on some installations the app crashes before a dialog can be displayed.
|
||||
Utils.showToastLong(str("gms_core_toast_not_installed_message"));
|
||||
open(getGmsCoreDownload());
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if GmsCore is running in the background.
|
||||
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
||||
if (client == null) {
|
||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||
|
||||
showBatteryOptimizationDialog(context,
|
||||
"gms_core_dialog_not_whitelisted_not_allowed_in_background_message",
|
||||
"gms_core_dialog_open_website_text",
|
||||
(dialog, id) -> open(DONT_KILL_MY_APP_LINK));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if GmsCore is whitelisted from battery optimizations.
|
||||
if (batteryOptimizationsEnabled(context)) {
|
||||
Logger.printInfo(() -> "GmsCore is not whitelisted from battery optimizations");
|
||||
showBatteryOptimizationDialog(context,
|
||||
"gms_core_dialog_not_whitelisted_using_battery_optimizations_message",
|
||||
"gms_core_dialog_continue_text",
|
||||
(dialog, id) -> openGmsCoreDisableBatteryOptimizationsIntent(context));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "checkGmsCore failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getGmsCoreDownloadLink() {
|
||||
final var vendor = getGmsCoreVendor();
|
||||
@SuppressLint("BatteryLife") // Permission is part of GmsCore
|
||||
private static void openGmsCoreDisableBatteryOptimizationsIntent(Activity activity) {
|
||||
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
|
||||
intent.setData(Uri.fromParts("package", GMS_CORE_PACKAGE_NAME, null));
|
||||
activity.startActivityForResult(intent, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If GmsCore is not whitelisted from battery optimizations.
|
||||
*/
|
||||
private static boolean batteryOptimizationsEnabled(Context context) {
|
||||
var powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||
return !powerManager.isIgnoringBatteryOptimizations(GMS_CORE_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
private static String getGmsCoreDownload() {
|
||||
final var vendorGroupId = getGmsCoreVendorGroupId();
|
||||
//noinspection SwitchStatementWithTooFewBranches
|
||||
switch (vendor) {
|
||||
switch (vendorGroupId) {
|
||||
case "app.revanced":
|
||||
return "https://github.com/revanced/gmscore/releases/latest";
|
||||
default:
|
||||
return vendor + ".android.gms";
|
||||
return vendorGroupId + ".android.gms";
|
||||
}
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
private static String getGmsCoreVendor() {
|
||||
private static String getGmsCoreVendorGroupId() {
|
||||
return "app.revanced";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import android.preference.PreferenceGroup;
|
|||
import android.preference.PreferenceScreen;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.FrameLayout;
|
||||
|
@ -28,6 +29,7 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import java.text.Bidi;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
|
@ -35,7 +37,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||
import kotlin.text.Regex;
|
||||
import app.revanced.integrations.shared.settings.preference.ReVancedAboutPreference;
|
||||
|
||||
public class Utils {
|
||||
|
||||
|
@ -47,30 +49,44 @@ public class Utils {
|
|||
private Utils() {
|
||||
} // utility class
|
||||
|
||||
public static String getVersionName() {
|
||||
if (versionName != null) return versionName;
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* @return The manifest 'Version' entry of the patches.jar used during patching.
|
||||
*/
|
||||
public static String getPatchesReleaseVersion() {
|
||||
return ""; // Value is replaced during patching.
|
||||
}
|
||||
|
||||
PackageInfo packageInfo;
|
||||
try {
|
||||
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
||||
/**
|
||||
* @return The version name of the app, such as "YouTube".
|
||||
*/
|
||||
public static String getAppVersionName() {
|
||||
if (versionName == null) {
|
||||
try {
|
||||
final var packageName = Objects.requireNonNull(getContext()).getPackageName();
|
||||
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
packageInfo = packageManager.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.PackageInfoFlags.of(0)
|
||||
);
|
||||
else
|
||||
packageInfo = packageManager.getPackageInfo(
|
||||
packageName,
|
||||
0
|
||||
);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Logger.printException(() -> "Failed to get package info", e);
|
||||
return null;
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
PackageInfo packageInfo;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageInfo = packageManager.getPackageInfo(
|
||||
packageName,
|
||||
PackageManager.PackageInfoFlags.of(0)
|
||||
);
|
||||
} else {
|
||||
packageInfo = packageManager.getPackageInfo(
|
||||
packageName,
|
||||
0
|
||||
);
|
||||
}
|
||||
versionName = packageInfo.versionName;
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to get package info", ex);
|
||||
versionName = "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
return versionName = packageInfo.versionName;
|
||||
return versionName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,7 +95,7 @@ public class Utils {
|
|||
* @param condition The setting to check for hiding the view.
|
||||
* @param view The view to hide.
|
||||
*/
|
||||
public static void hideViewBy1dpUnderCondition(BooleanSetting condition, View view) {
|
||||
public static void hideViewBy0dpUnderCondition(BooleanSetting condition, View view) {
|
||||
if (!condition.get()) return;
|
||||
|
||||
Logger.printDebug(() -> "Hiding view with setting: " + condition);
|
||||
|
@ -101,6 +117,15 @@ public class Utils {
|
|||
view.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
public static void removeViewFromParentUnderConditions(BooleanSetting setting, View view) {
|
||||
if (setting.get()) {
|
||||
ViewParent parent = view.getParent();
|
||||
if (parent instanceof ViewGroup) {
|
||||
((ViewGroup) parent).removeView(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* General purpose pool for network calls and other background tasks.
|
||||
* All tasks run at max thread priority.
|
||||
|
@ -185,6 +210,7 @@ public class Utils {
|
|||
}
|
||||
|
||||
public static int getResourceColor(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
||||
//noinspection deprecation
|
||||
return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color"));
|
||||
}
|
||||
|
||||
|
@ -323,7 +349,7 @@ public class Utils {
|
|||
try {
|
||||
runnable.run();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> runnable.getClass() + ": " + ex.getMessage(), ex);
|
||||
Logger.printException(() -> runnable.getClass().getSimpleName() + ": " + ex.getMessage(), ex);
|
||||
}
|
||||
};
|
||||
new Handler(Looper.getMainLooper()).postDelayed(loggingRunnable, delayMillis);
|
||||
|
@ -396,27 +422,30 @@ public class Utils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Hide a view by setting its layout params to 1x1
|
||||
* Hide a view by setting its layout params to 0x0
|
||||
* @param view The view to hide.
|
||||
*/
|
||||
public static void hideViewByLayoutParams(View view) {
|
||||
if (view instanceof LinearLayout) {
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(1, 1);
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams);
|
||||
} else if (view instanceof FrameLayout) {
|
||||
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(1, 1);
|
||||
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams2);
|
||||
} else if (view instanceof RelativeLayout) {
|
||||
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(1, 1);
|
||||
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams3);
|
||||
} else if (view instanceof Toolbar) {
|
||||
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(1, 1);
|
||||
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams4);
|
||||
} else if (view instanceof ViewGroup) {
|
||||
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(1, 1);
|
||||
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(0, 0);
|
||||
view.setLayoutParams(layoutParams5);
|
||||
} else {
|
||||
Logger.printDebug(() -> "Hidden view with id " + view.getId());
|
||||
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
params.width = 0;
|
||||
params.height = 0;
|
||||
view.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,11 +474,8 @@ public class Utils {
|
|||
this.keySuffix = keySuffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaults to {@link #UNSORTED} if key is null or has no sort suffix.
|
||||
*/
|
||||
@NonNull
|
||||
static Sort fromKey(@Nullable String key) {
|
||||
static Sort fromKey(@Nullable String key, @NonNull Sort defaultSort) {
|
||||
if (key != null) {
|
||||
for (Sort sort : values()) {
|
||||
if (key.endsWith(sort.keySuffix)) {
|
||||
|
@ -457,18 +483,18 @@ public class Utils {
|
|||
}
|
||||
}
|
||||
}
|
||||
return UNSORTED;
|
||||
return defaultSort;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Regex punctuationRegex = new Regex("\\p{P}+");
|
||||
private static final Pattern punctuationPattern = Pattern.compile("\\p{P}+");
|
||||
|
||||
/**
|
||||
* Strips all punctuation and converts to lower case. A null parameter returns an empty string.
|
||||
*/
|
||||
public static String removePunctuationConvertToLowercase(@Nullable CharSequence original) {
|
||||
if (original == null) return "";
|
||||
return punctuationRegex.replace(original, "").toLowerCase();
|
||||
return punctuationPattern.matcher(original).replaceAll("").toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -479,19 +505,26 @@ public class Utils {
|
|||
* If a preference has no key or no {@link Sort} suffix,
|
||||
* then the preferences are left unsorted.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public static void sortPreferenceGroups(@NonNull PreferenceGroup group) {
|
||||
Sort sort = Sort.fromKey(group.getKey());
|
||||
Sort groupSort = Sort.fromKey(group.getKey(), Sort.UNSORTED);
|
||||
SortedMap<String, Preference> preferences = new TreeMap<>();
|
||||
|
||||
for (int i = 0, prefCount = group.getPreferenceCount(); i < prefCount; i++) {
|
||||
Preference preference = group.getPreference(i);
|
||||
|
||||
final Sort preferenceSort;
|
||||
if (preference instanceof PreferenceGroup) {
|
||||
sortPreferenceGroups((PreferenceGroup) preference);
|
||||
preferenceSort = groupSort; // Sort value for groups is for it's content, not itself.
|
||||
} else {
|
||||
// Allow individual preferences to set a key sorting.
|
||||
// Used to force a preference to the top or bottom of a group.
|
||||
preferenceSort = Sort.fromKey(preference.getKey(), groupSort);
|
||||
}
|
||||
|
||||
final String sortValue;
|
||||
switch (sort) {
|
||||
switch (preferenceSort) {
|
||||
case BY_TITLE:
|
||||
sortValue = removePunctuationConvertToLowercase(preference.getTitle());
|
||||
break;
|
||||
|
@ -511,8 +544,9 @@ public class Utils {
|
|||
for (Preference pref : preferences.values()) {
|
||||
int order = index++;
|
||||
|
||||
// If the preference is a PreferenceScreen or is an intent preference, move to the top.
|
||||
if (pref instanceof PreferenceScreen || pref.getIntent() != null) {
|
||||
// Move any screens, intents, and the one off About preference to the top.
|
||||
if (pref instanceof PreferenceScreen || pref instanceof ReVancedAboutPreference
|
||||
|| pref.getIntent() != null) {
|
||||
// Arbitrary high number.
|
||||
order -= 1000;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package app.revanced.integrations.shared.settings;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* If an Enum value is removed or changed, any saved or imported data using the
|
||||
* non-existent value will be reverted to the default value
|
||||
* (the event is logged, but no user error is displayed).
|
||||
*
|
||||
* All saved JSON text is converted to lowercase to keep the output less obnoxious.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class EnumSetting<T extends Enum<?>> extends Setting<T> {
|
||||
public EnumSetting(String key, T defaultValue) {
|
||||
super(key, defaultValue);
|
||||
}
|
||||
public EnumSetting(String key, T defaultValue, boolean rebootApp) {
|
||||
super(key, defaultValue, rebootApp);
|
||||
}
|
||||
public EnumSetting(String key, T defaultValue, boolean rebootApp, boolean includeWithImportExport) {
|
||||
super(key, defaultValue, rebootApp, includeWithImportExport);
|
||||
}
|
||||
public EnumSetting(String key, T defaultValue, String userDialogMessage) {
|
||||
super(key, defaultValue, userDialogMessage);
|
||||
}
|
||||
public EnumSetting(String key, T defaultValue, Availability availability) {
|
||||
super(key, defaultValue, availability);
|
||||
}
|
||||
public EnumSetting(String key, T defaultValue, boolean rebootApp, String userDialogMessage) {
|
||||
super(key, defaultValue, rebootApp, userDialogMessage);
|
||||
}
|
||||
public EnumSetting(String key, T defaultValue, boolean rebootApp, Availability availability) {
|
||||
super(key, defaultValue, rebootApp, availability);
|
||||
}
|
||||
public EnumSetting(String key, T defaultValue, boolean rebootApp, String userDialogMessage, Availability availability) {
|
||||
super(key, defaultValue, rebootApp, userDialogMessage, availability);
|
||||
}
|
||||
public EnumSetting(@NonNull String key, @NonNull T defaultValue, boolean rebootApp, boolean includeWithImportExport, @Nullable String userDialogMessage, @Nullable Availability availability) {
|
||||
super(key, defaultValue, rebootApp, includeWithImportExport, userDialogMessage, availability);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void load() {
|
||||
value = preferences.getEnum(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T readFromJSON(JSONObject json, String importExportKey) throws JSONException {
|
||||
String enumName = json.getString(importExportKey);
|
||||
try {
|
||||
return getEnumFromString(enumName);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Info level to allow removing enum values in the future without showing any user errors.
|
||||
Logger.printInfo(() -> "Using default, and ignoring unknown enum value: " + enumName, ex);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeToJSON(JSONObject json, String importExportKey) throws JSONException {
|
||||
// Use lowercase to keep the output less ugly.
|
||||
json.put(importExportKey, value.name().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private T getEnumFromString(String enumName) {
|
||||
//noinspection ConstantConditions
|
||||
for (Enum<?> value : defaultValue.getClass().getEnumConstants()) {
|
||||
if (value.name().equalsIgnoreCase(enumName)) {
|
||||
// noinspection unchecked
|
||||
return (T) value;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown enum value: " + enumName);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setValueFromString(@NonNull String newValue) {
|
||||
value = getEnumFromString(Objects.requireNonNull(newValue));
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public T get() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -224,6 +224,7 @@ public abstract class Setting<T> {
|
|||
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
Object newValue = setting.get();
|
||||
final Object migratedValue;
|
||||
if (setting instanceof BooleanSetting) {
|
||||
|
@ -238,13 +239,17 @@ public abstract class Setting<T> {
|
|||
migratedValue = oldPrefs.getString(settingKey, (String) newValue);
|
||||
} else {
|
||||
Logger.printException(() -> "Unknown setting: " + setting);
|
||||
// Remove otherwise it'll show a toast on every launch
|
||||
oldPrefs.preferences.edit().remove(settingKey).apply();
|
||||
return;
|
||||
}
|
||||
|
||||
oldPrefs.preferences.edit().remove(settingKey).apply(); // Remove the old setting.
|
||||
if (migratedValue.equals(newValue)) {
|
||||
Logger.printDebug(() -> "Value does not need migrating: " + settingKey);
|
||||
return; // Old value is already equal to the new setting value.
|
||||
}
|
||||
|
||||
Logger.printDebug(() -> "Migrating old preference value into current preference: " + settingKey);
|
||||
//noinspection unchecked
|
||||
setting.save(migratedValue);
|
||||
|
@ -324,6 +329,7 @@ public abstract class Setting<T> {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param importExportKey The JSON key. The JSONObject parameter will contain data for this key.
|
||||
* @return the value stored using the import/export key. Do not set any values in this method.
|
||||
*/
|
||||
protected abstract T readFromJSON(JSONObject json, String importExportKey) throws JSONException;
|
||||
|
|
|
@ -16,10 +16,7 @@ import app.revanced.integrations.shared.settings.Setting;
|
|||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @noinspection deprecation, DataFlowIssue , unused */
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||
/**
|
||||
* Indicates that if a preference changes,
|
||||
|
|
|
@ -15,7 +15,7 @@ import app.revanced.integrations.shared.Utils;
|
|||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
/** @noinspection deprecation, unused */
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
||||
|
||||
private String existingSettings;
|
||||
|
|
|
@ -0,0 +1,312 @@
|
|||
package app.revanced.integrations.shared.settings.preference;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import static app.revanced.integrations.youtube.requests.Route.Method.GET;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Dialog;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.Preference;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Window;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.requests.Requester;
|
||||
import app.revanced.integrations.youtube.requests.Route;
|
||||
|
||||
/**
|
||||
* Opens a dialog showing the links from {@link SocialLinksRoutes}.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class ReVancedAboutPreference extends Preference {
|
||||
|
||||
private static String useNonBreakingHyphens(String text) {
|
||||
// Replace any dashes with non breaking dashes, so the English text 'pre-release'
|
||||
// and the dev release number does not break and cover two lines.
|
||||
return text.replace("-", "‑"); // #8209 = non breaking hyphen.
|
||||
}
|
||||
|
||||
private static String getColorHexString(int color) {
|
||||
return String.format("#%06X", (0x00FFFFFF & color));
|
||||
}
|
||||
|
||||
protected boolean isDarkModeEnabled() {
|
||||
Configuration config = getContext().getResources().getConfiguration();
|
||||
final int currentNightMode = config.uiMode & Configuration.UI_MODE_NIGHT_MASK;
|
||||
return currentNightMode == Configuration.UI_MODE_NIGHT_YES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can override this and provide a themed color.
|
||||
*/
|
||||
protected int getLightColor() {
|
||||
return Color.WHITE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses can override this and provide a themed color.
|
||||
*/
|
||||
protected int getDarkColor() {
|
||||
return Color.BLACK;
|
||||
}
|
||||
|
||||
private String createDialogHtml(ReVancedSocialLink[] socialLinks) {
|
||||
final boolean isNetworkConnected = Utils.isNetworkConnected();
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append("<html>");
|
||||
builder.append("<body style=\"text-align: center; padding: 10px;\">");
|
||||
|
||||
final boolean isDarkMode = isDarkModeEnabled();
|
||||
String backgroundColorHex = getColorHexString(isDarkMode ? getDarkColor() : getLightColor());
|
||||
String foregroundColorHex = getColorHexString(isDarkMode ? getLightColor() : getDarkColor());
|
||||
// Apply light/dark mode colors.
|
||||
builder.append(String.format(
|
||||
"<style> body { background-color: %s; color: %s; } a { color: %s; } </style>",
|
||||
backgroundColorHex, foregroundColorHex, foregroundColorHex));
|
||||
|
||||
if (isNetworkConnected) {
|
||||
builder.append("<img style=\"width: 100px; height: 100px;\" "
|
||||
// Hide the image if it does not load.
|
||||
+ "onerror=\"this.style.display='none';\" "
|
||||
+ "src=\"https://revanced.app/favicon.ico\" />");
|
||||
}
|
||||
|
||||
String patchesVersion = Utils.getPatchesReleaseVersion();
|
||||
|
||||
// Add the title.
|
||||
builder.append("<h1>")
|
||||
.append("ReVanced")
|
||||
.append("</h1>");
|
||||
|
||||
builder.append("<p>")
|
||||
// Replace hyphens with non breaking dashes so the version number does not break lines.
|
||||
.append(useNonBreakingHyphens(str("revanced_settings_about_links_body", patchesVersion)))
|
||||
.append("</p>");
|
||||
|
||||
// Add a disclaimer if using a dev release.
|
||||
if (patchesVersion.contains("dev")) {
|
||||
builder.append("<h3>")
|
||||
// English text 'Pre-release' can break lines.
|
||||
.append(useNonBreakingHyphens(str("revanced_settings_about_links_dev_header")))
|
||||
.append("</h3>");
|
||||
|
||||
builder.append("<p>")
|
||||
.append(str("revanced_settings_about_links_dev_body"))
|
||||
.append("</p>");
|
||||
}
|
||||
|
||||
builder.append("<h2 style=\"margin-top: 30px;\">")
|
||||
.append(str("revanced_settings_about_links_header"))
|
||||
.append("</h2>");
|
||||
|
||||
builder.append("<div>");
|
||||
for (ReVancedSocialLink social : socialLinks) {
|
||||
builder.append("<div style=\"margin-bottom: 20px;\">");
|
||||
builder.append(String.format("<a href=\"%s\">%s</a>", social.url, social.name));
|
||||
builder.append("</div>");
|
||||
}
|
||||
builder.append("</div>");
|
||||
|
||||
builder.append("</body></html>");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
// Show a progress spinner if the social links are not fetched yet.
|
||||
if (!SocialLinksRoutes.hasFetchedLinks() && Utils.isNetworkConnected()) {
|
||||
ProgressDialog progress = new ProgressDialog(getContext());
|
||||
progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
|
||||
progress.show();
|
||||
Utils.runOnBackgroundThread(() -> fetchLinksAndShowDialog(progress));
|
||||
} else {
|
||||
// No network call required and can run now.
|
||||
fetchLinksAndShowDialog(null);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchLinksAndShowDialog(@Nullable ProgressDialog progress) {
|
||||
ReVancedSocialLink[] socialLinks = SocialLinksRoutes.fetchSocialLinks();
|
||||
String htmlDialog = createDialogHtml(socialLinks);
|
||||
|
||||
Utils.runOnMainThreadNowOrLater(() -> {
|
||||
if (progress != null) {
|
||||
progress.dismiss();
|
||||
}
|
||||
new WebViewDialog(getContext(), htmlDialog).show();
|
||||
});
|
||||
}
|
||||
|
||||
public ReVancedAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public ReVancedAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public ReVancedAboutPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public ReVancedAboutPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays html content as a dialog. Any links a user taps on are opened in an external browser.
|
||||
*/
|
||||
class WebViewDialog extends Dialog {
|
||||
|
||||
private final String htmlContent;
|
||||
|
||||
public WebViewDialog(@NonNull Context context, @NonNull String htmlContent) {
|
||||
super(context);
|
||||
this.htmlContent = htmlContent;
|
||||
}
|
||||
|
||||
// JS required to hide any broken images. No remote javascript is ever loaded.
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
WebView webView = new WebView(getContext());
|
||||
webView.getSettings().setJavaScriptEnabled(true);
|
||||
webView.setWebViewClient(new OpenLinksExternallyWebClient());
|
||||
webView.loadDataWithBaseURL(null, htmlContent, "text/html", "utf-8", null);
|
||||
|
||||
setContentView(webView);
|
||||
}
|
||||
|
||||
private class OpenLinksExternallyWebClient extends WebViewClient {
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
try {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
getContext().startActivity(intent);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Open link failure", ex);
|
||||
}
|
||||
// Dismiss the about dialog using a delay,
|
||||
// otherwise without a delay the UI looks hectic with the dialog dismissing
|
||||
// to show the settings while simultaneously a web browser is opening.
|
||||
Utils.runOnMainThreadDelayed(WebViewDialog.this::dismiss, 500);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ReVancedSocialLink {
|
||||
final boolean preferred;
|
||||
final String name;
|
||||
final String url;
|
||||
|
||||
ReVancedSocialLink(JSONObject json) throws JSONException {
|
||||
this(json.getBoolean("preferred"),
|
||||
json.getString("name"),
|
||||
json.getString("url")
|
||||
);
|
||||
}
|
||||
|
||||
ReVancedSocialLink(boolean preferred, String name, String url) {
|
||||
this.preferred = preferred;
|
||||
this.name = name;
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReVancedSocialLink{" +
|
||||
"preferred=" + preferred +
|
||||
", name='" + name + '\'' +
|
||||
", url='" + url + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
class SocialLinksRoutes {
|
||||
/**
|
||||
* Links to use if fetch links api call fails.
|
||||
*/
|
||||
private static final ReVancedSocialLink[] NO_CONNECTION_STATIC_LINKS = {
|
||||
new ReVancedSocialLink(true, "ReVanced.app", "https://revanced.app")
|
||||
};
|
||||
|
||||
private static final String SOCIAL_LINKS_PROVIDER = "https://api.revanced.app/v2";
|
||||
private static final Route.CompiledRoute GET_SOCIAL = new Route(GET, "/socials").compile();
|
||||
|
||||
@Nullable
|
||||
private static volatile ReVancedSocialLink[] fetchedLinks;
|
||||
|
||||
static boolean hasFetchedLinks() {
|
||||
return fetchedLinks != null;
|
||||
}
|
||||
|
||||
static ReVancedSocialLink[] fetchSocialLinks() {
|
||||
try {
|
||||
if (hasFetchedLinks()) return fetchedLinks;
|
||||
|
||||
// Check if there is no internet connection.
|
||||
if (!Utils.isNetworkConnected()) return NO_CONNECTION_STATIC_LINKS;
|
||||
|
||||
HttpURLConnection connection = Requester.getConnectionFromCompiledRoute(SOCIAL_LINKS_PROVIDER, GET_SOCIAL);
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setReadTimeout(5000);
|
||||
Logger.printDebug(() -> "Fetching social links from: " + connection.getURL());
|
||||
|
||||
// Do not show an exception toast if the server is down
|
||||
final int responseCode = connection.getResponseCode();
|
||||
if (responseCode != 200) {
|
||||
Logger.printDebug(() -> "Failed to get social links. Response code: " + responseCode);
|
||||
return NO_CONNECTION_STATIC_LINKS;
|
||||
}
|
||||
|
||||
JSONObject json = Requester.parseJSONObjectAndDisconnect(connection);
|
||||
JSONArray socials = json.getJSONArray("socials");
|
||||
|
||||
List<ReVancedSocialLink> links = new ArrayList<>();
|
||||
for (int i = 0, length = socials.length(); i < length; i++) {
|
||||
ReVancedSocialLink link = new ReVancedSocialLink(socials.getJSONObject(i));
|
||||
links.add(link);
|
||||
}
|
||||
Logger.printDebug(() -> "links: " + links);
|
||||
|
||||
return fetchedLinks = links.toArray(new ReVancedSocialLink[0]);
|
||||
|
||||
} catch (SocketTimeoutException ex) {
|
||||
Logger.printInfo(() -> "Could not fetch social links", ex); // No toast.
|
||||
} catch (JSONException ex) {
|
||||
Logger.printException(() -> "Could not parse about information", ex);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Failed to get about information", ex);
|
||||
}
|
||||
|
||||
return NO_CONNECTION_STATIC_LINKS;
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ import java.util.Objects;
|
|||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class ResettableEditTextPreference extends EditTextPreference {
|
||||
|
||||
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
|
@ -33,7 +33,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
Setting setting = Setting.getSettingFromPath(getKey());
|
||||
Setting<?> setting = Setting.getSettingFromPath(getKey());
|
||||
if (setting != null) {
|
||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||
}
|
||||
button.setOnClickListener(v -> {
|
||||
try {
|
||||
Setting setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
||||
Setting<?> setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
||||
String defaultStringValue = setting.defaultValue.toString();
|
||||
EditText editText = getEditText();
|
||||
editText.setText(defaultStringValue);
|
||||
|
|
|
@ -32,17 +32,31 @@ public class SharedPrefCategory {
|
|||
|
||||
private void removeConflictingPreferenceKeyValue(@NonNull String key) {
|
||||
Logger.printException(() -> "Found conflicting preference: " + key);
|
||||
preferences.edit().remove(key).apply();
|
||||
removeKey(key);
|
||||
}
|
||||
|
||||
private void saveObjectAsString(@NonNull String key, @Nullable Object value) {
|
||||
preferences.edit().putString(key, (value == null ? null : value.toString())).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any preference data type that has the specified key.
|
||||
*/
|
||||
public void removeKey(@NonNull String key) {
|
||||
preferences.edit().remove(Objects.requireNonNull(key)).apply();
|
||||
}
|
||||
|
||||
public void saveBoolean(@NonNull String key, boolean value) {
|
||||
preferences.edit().putBoolean(key, value).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value a NULL parameter removes the value from the preferences
|
||||
*/
|
||||
public void saveEnumAsString(@NonNull String key, @Nullable Enum<?> value) {
|
||||
saveObjectAsString(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value a NULL parameter removes the value from the preferences
|
||||
*/
|
||||
|
@ -83,6 +97,28 @@ public class SharedPrefCategory {
|
|||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public <T extends Enum<?>> T getEnum(@NonNull String key, @NonNull T _default) {
|
||||
Objects.requireNonNull(_default);
|
||||
try {
|
||||
String enumName = preferences.getString(key, null);
|
||||
if (enumName != null) {
|
||||
try {
|
||||
// noinspection unchecked
|
||||
return (T) Enum.valueOf(_default.getClass(), enumName);
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Info level to allow removing enum values in the future without showing any user errors.
|
||||
Logger.printInfo(() -> "Using default, and ignoring unknown enum value: " + enumName);
|
||||
removeKey(key);
|
||||
}
|
||||
}
|
||||
} catch (ClassCastException ex) {
|
||||
// Value stored is a completely different type (should never happen).
|
||||
removeConflictingPreferenceKeyValue(key);
|
||||
}
|
||||
return _default;
|
||||
}
|
||||
|
||||
public boolean getBoolean(@NonNull String key, boolean _default) {
|
||||
try {
|
||||
return preferences.getBoolean(key, _default);
|
||||
|
@ -100,17 +136,16 @@ public class SharedPrefCategory {
|
|||
if (value != null) {
|
||||
return Integer.valueOf(value);
|
||||
}
|
||||
return _default;
|
||||
} catch (ClassCastException ex) {
|
||||
} catch (ClassCastException | NumberFormatException ex) {
|
||||
try {
|
||||
// Old data previously stored as primitive.
|
||||
return preferences.getInt(key, _default);
|
||||
} catch (ClassCastException ex2) {
|
||||
// Value stored is a completely different type (should never happen).
|
||||
removeConflictingPreferenceKeyValue(key);
|
||||
return _default;
|
||||
}
|
||||
}
|
||||
return _default;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -120,15 +155,14 @@ public class SharedPrefCategory {
|
|||
if (value != null) {
|
||||
return Long.valueOf(value);
|
||||
}
|
||||
return _default;
|
||||
} catch (ClassCastException ex) {
|
||||
} catch (ClassCastException | NumberFormatException ex) {
|
||||
try {
|
||||
return preferences.getLong(key, _default);
|
||||
} catch (ClassCastException ex2) {
|
||||
removeConflictingPreferenceKeyValue(key);
|
||||
return _default;
|
||||
}
|
||||
}
|
||||
return _default;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@ -138,15 +172,14 @@ public class SharedPrefCategory {
|
|||
if (value != null) {
|
||||
return Float.valueOf(value);
|
||||
}
|
||||
return _default;
|
||||
} catch (ClassCastException ex) {
|
||||
} catch (ClassCastException | NumberFormatException ex) {
|
||||
try {
|
||||
return preferences.getFloat(key, _default);
|
||||
} catch (ClassCastException ex2) {
|
||||
removeConflictingPreferenceKeyValue(key);
|
||||
return _default;
|
||||
}
|
||||
}
|
||||
return _default;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
|
@ -1,14 +1,24 @@
|
|||
package app.revanced.integrations.youtube;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Color;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
|
||||
public class ThemeHelper {
|
||||
@Nullable
|
||||
private static Integer darkThemeColor, lightThemeColor;
|
||||
private static int themeValue;
|
||||
|
||||
public static void setTheme(Object value) {
|
||||
final int newOrdinalValue = ((Enum) value).ordinal();
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void setTheme(Enum<?> value) {
|
||||
final int newOrdinalValue = value.ordinal();
|
||||
if (themeValue != newOrdinalValue) {
|
||||
themeValue = newOrdinalValue;
|
||||
Logger.printDebug(() -> "Theme value: " + newOrdinalValue);
|
||||
|
@ -26,4 +36,48 @@ public class ThemeHelper {
|
|||
activity.setTheme(Utils.getResourceIdentifier(theme, "style"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
private static String darkThemeResourceName() {
|
||||
// Value is changed by Theme patch, if included.
|
||||
return "@color/yt_black3";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The dark theme color as specified by the Theme patch (if included),
|
||||
* or the dark mode background color unpatched YT uses.
|
||||
*/
|
||||
public static int getDarkThemeColor() {
|
||||
if (darkThemeColor == null) {
|
||||
darkThemeColor = getColorInt(darkThemeResourceName());
|
||||
}
|
||||
return darkThemeColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
private static String lightThemeResourceName() {
|
||||
// Value is changed by Theme patch, if included.
|
||||
return "@color/yt_white1";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The light theme color as specified by the Theme patch (if included),
|
||||
* or the non dark mode background color unpatched YT uses.
|
||||
*/
|
||||
public static int getLightThemeColor() {
|
||||
if (lightThemeColor == null) {
|
||||
lightThemeColor = getColorInt(lightThemeResourceName());
|
||||
}
|
||||
return lightThemeColor;
|
||||
}
|
||||
|
||||
private static int getColorInt(String colorString) {
|
||||
if (colorString.startsWith("#")) {
|
||||
return Color.parseColor(colorString);
|
||||
}
|
||||
return Utils.getResourceColor(colorString);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import static app.revanced.integrations.youtube.settings.Settings.*;
|
||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import org.chromium.net.UrlRequest;
|
||||
import org.chromium.net.UrlResponseInfo;
|
||||
import org.chromium.net.impl.CronetUrlRequest;
|
||||
|
@ -20,7 +22,12 @@ import java.util.LinkedHashMap;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.shared.settings.Setting;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||
|
||||
/**
|
||||
* Alternative YouTube thumbnails.
|
||||
|
@ -39,16 +46,72 @@ import static app.revanced.integrations.shared.StringRef.str;
|
|||
* If a failed thumbnail load is reloaded (ie: scroll off, then on screen), then the original thumbnail
|
||||
* is reloaded instead. Fast thumbnails requires using SD or lower thumbnail resolution,
|
||||
* because a noticeable number of videos do not have hq720 and too much fail to load.
|
||||
* <p>
|
||||
* Ideas for improvements:
|
||||
* - Selectively allow using original thumbnails in some situations,
|
||||
* such as videos subscription feed, watch history, or in search results.
|
||||
* - Save to a temporary file the video id's verified to have alt thumbnails.
|
||||
* This would speed up loading the watch history and users saved playlists.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public final class AlternativeThumbnailsPatch {
|
||||
|
||||
// These must be class declarations if declared here,
|
||||
// otherwise the app will not load due to cyclic initialization errors.
|
||||
public static final class DeArrowAvailability implements Setting.Availability {
|
||||
public static boolean usingDeArrowAnywhere() {
|
||||
return ALT_THUMBNAIL_HOME.get().useDeArrow
|
||||
|| ALT_THUMBNAIL_SUBSCRIPTIONS.get().useDeArrow
|
||||
|| ALT_THUMBNAIL_LIBRARY.get().useDeArrow
|
||||
|| ALT_THUMBNAIL_PLAYER.get().useDeArrow
|
||||
|| ALT_THUMBNAIL_SEARCH.get().useDeArrow;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return usingDeArrowAnywhere();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class StillImagesAvailability implements Setting.Availability {
|
||||
public static boolean usingStillImagesAnywhere() {
|
||||
return ALT_THUMBNAIL_HOME.get().useStillImages
|
||||
|| ALT_THUMBNAIL_SUBSCRIPTIONS.get().useStillImages
|
||||
|| ALT_THUMBNAIL_LIBRARY.get().useStillImages
|
||||
|| ALT_THUMBNAIL_PLAYER.get().useStillImages
|
||||
|| ALT_THUMBNAIL_SEARCH.get().useStillImages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return usingStillImagesAnywhere();
|
||||
}
|
||||
}
|
||||
|
||||
public enum ThumbnailOption {
|
||||
ORIGINAL(false, false),
|
||||
DEARROW(true, false),
|
||||
DEARROW_STILL_IMAGES(true, true),
|
||||
STILL_IMAGES(false, true);
|
||||
|
||||
final boolean useDeArrow;
|
||||
final boolean useStillImages;
|
||||
|
||||
ThumbnailOption(boolean useDeArrow, boolean useStillImages) {
|
||||
this.useDeArrow = useDeArrow;
|
||||
this.useStillImages = useStillImages;
|
||||
}
|
||||
}
|
||||
|
||||
public enum ThumbnailStillTime {
|
||||
BEGINNING(1),
|
||||
MIDDLE(2),
|
||||
END(3);
|
||||
|
||||
/**
|
||||
* The url alt image number. Such as the 2 in 'hq720_2.jpg'
|
||||
*/
|
||||
final int altImageNumber;
|
||||
|
||||
ThumbnailStillTime(int altImageNumber) {
|
||||
this.altImageNumber = altImageNumber;
|
||||
}
|
||||
}
|
||||
|
||||
private static final Uri dearrowApiUri;
|
||||
|
||||
/**
|
||||
|
@ -78,13 +141,6 @@ public final class AlternativeThumbnailsPatch {
|
|||
* Fix any bad imported data.
|
||||
*/
|
||||
private static Uri validateSettings() {
|
||||
final int altThumbnailType = Settings.ALT_THUMBNAIL_STILLS_TIME.get();
|
||||
if (altThumbnailType < 1 || altThumbnailType > 3) {
|
||||
Utils.showToastLong("Invalid Alternative still thumbnail type: "
|
||||
+ altThumbnailType + ". Using default");
|
||||
Settings.ALT_THUMBNAIL_STILLS_TIME.resetToDefault();
|
||||
}
|
||||
|
||||
Uri apiUri = Uri.parse(Settings.ALT_THUMBNAIL_DEARROW_API_URL.get());
|
||||
// Cannot use unsecured 'http', otherwise the connections fail to start and no callbacks hooks are made.
|
||||
String scheme = apiUri.getScheme();
|
||||
|
@ -96,12 +152,38 @@ public final class AlternativeThumbnailsPatch {
|
|||
return apiUri;
|
||||
}
|
||||
|
||||
private static boolean usingDeArrow() {
|
||||
return Settings.ALT_THUMBNAIL_DEARROW.get();
|
||||
}
|
||||
private static ThumbnailOption optionSettingForCurrentNavigation() {
|
||||
// Must check player type first, as search bar can be active behind the player.
|
||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||
return ALT_THUMBNAIL_PLAYER.get();
|
||||
}
|
||||
|
||||
private static boolean usingVideoStills() {
|
||||
return Settings.ALT_THUMBNAIL_STILLS.get();
|
||||
// Must check second, as search can be from any tab.
|
||||
if (NavigationBar.isSearchBarActive()) {
|
||||
return ALT_THUMBNAIL_SEARCH.get();
|
||||
}
|
||||
|
||||
// Avoid checking which navigation button is selected, if all other settings are the same.
|
||||
ThumbnailOption homeOption = ALT_THUMBNAIL_HOME.get();
|
||||
ThumbnailOption subscriptionsOption = ALT_THUMBNAIL_SUBSCRIPTIONS.get();
|
||||
ThumbnailOption libraryOption = ALT_THUMBNAIL_LIBRARY.get();
|
||||
if ((homeOption == subscriptionsOption) && (homeOption == libraryOption)) {
|
||||
return homeOption; // All are the same option.
|
||||
}
|
||||
|
||||
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||
if (selectedNavButton == null) {
|
||||
// Unknown tab, treat as the home tab;
|
||||
return homeOption;
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.HOME) {
|
||||
return homeOption;
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS || selectedNavButton == NavigationButton.NOTIFICATIONS) {
|
||||
return subscriptionsOption;
|
||||
}
|
||||
// A library tab variant is active.
|
||||
return libraryOption;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -179,9 +261,9 @@ public final class AlternativeThumbnailsPatch {
|
|||
*/
|
||||
public static String overrideImageURL(String originalUrl) {
|
||||
try {
|
||||
final boolean usingDeArrow = usingDeArrow();
|
||||
final boolean usingVideoStills = usingVideoStills();
|
||||
if (!usingDeArrow && !usingVideoStills) {
|
||||
ThumbnailOption option = optionSettingForCurrentNavigation();
|
||||
|
||||
if (option == ThumbnailOption.ORIGINAL) {
|
||||
return originalUrl;
|
||||
}
|
||||
|
||||
|
@ -200,14 +282,14 @@ public final class AlternativeThumbnailsPatch {
|
|||
|
||||
String sanitizedReplacementUrl;
|
||||
final boolean includeTracking;
|
||||
if (usingDeArrow && canUseDeArrowAPI()) {
|
||||
if (option.useDeArrow && canUseDeArrowAPI()) {
|
||||
includeTracking = false; // Do not include view tracking parameters with API call.
|
||||
final String fallbackUrl = usingVideoStills
|
||||
final String fallbackUrl = option.useStillImages
|
||||
? buildYoutubeVideoStillURL(decodedUrl, qualityToUse)
|
||||
: decodedUrl.sanitizedUrl;
|
||||
|
||||
sanitizedReplacementUrl = buildDeArrowThumbnailURL(decodedUrl.videoId, fallbackUrl);
|
||||
} else if (usingVideoStills) {
|
||||
} else if (option.useStillImages) {
|
||||
includeTracking = true; // Include view tracking parameters if present.
|
||||
sanitizedReplacementUrl = buildYoutubeVideoStillURL(decodedUrl, qualityToUse);
|
||||
} else {
|
||||
|
@ -240,7 +322,7 @@ public final class AlternativeThumbnailsPatch {
|
|||
|
||||
String url = responseInfo.getUrl();
|
||||
|
||||
if (usingDeArrow() && urlIsDeArrow(url)) {
|
||||
if (urlIsDeArrow(url)) {
|
||||
Logger.printDebug(() -> "handleCronetSuccess, statusCode: " + statusCode);
|
||||
if (statusCode == 304) {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304
|
||||
|
@ -250,7 +332,7 @@ public final class AlternativeThumbnailsPatch {
|
|||
return;
|
||||
}
|
||||
|
||||
if (usingVideoStills() && statusCode == 404) {
|
||||
if (statusCode == 404) {
|
||||
// Fast alt thumbnails is enabled and the thumbnail is not available.
|
||||
// The video is:
|
||||
// - live stream
|
||||
|
@ -294,15 +376,13 @@ public final class AlternativeThumbnailsPatch {
|
|||
@Nullable UrlResponseInfo responseInfo,
|
||||
IOException exception) {
|
||||
try {
|
||||
if (usingDeArrow()) {
|
||||
String url = ((CronetUrlRequest) request).getHookedUrl();
|
||||
if (urlIsDeArrow(url)) {
|
||||
Logger.printDebug(() -> "handleCronetFailure, exception: " + exception);
|
||||
final int statusCode = (responseInfo != null)
|
||||
? responseInfo.getHttpStatusCode()
|
||||
: 0;
|
||||
handleDeArrowError(url, statusCode);
|
||||
}
|
||||
String url = ((CronetUrlRequest) request).getHookedUrl();
|
||||
if (urlIsDeArrow(url)) {
|
||||
Logger.printDebug(() -> "handleCronetFailure, exception: " + exception);
|
||||
final int statusCode = (responseInfo != null)
|
||||
? responseInfo.getHttpStatusCode()
|
||||
: 0;
|
||||
handleDeArrowError(url, statusCode);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Callback failure error", ex);
|
||||
|
@ -332,13 +412,13 @@ public final class AlternativeThumbnailsPatch {
|
|||
for (ThumbnailQuality quality : values()) {
|
||||
originalNameToEnum.put(quality.originalName, quality);
|
||||
|
||||
for (int i = 1; i <= 3; i++) {
|
||||
for (ThumbnailStillTime time : ThumbnailStillTime.values()) {
|
||||
// 'custom' thumbnails set by the content creator.
|
||||
// These show up in place of regular thumbnails
|
||||
// and seem to be limited to [1, 3] range.
|
||||
originalNameToEnum.put(quality.originalName + "_custom_" + i, quality);
|
||||
// and seem to be limited to the same [1, 3] range as the still captures.
|
||||
originalNameToEnum.put(quality.originalName + "_custom_" + time.altImageNumber, quality);
|
||||
|
||||
altNameToEnum.put(quality.altImageName + i, quality);
|
||||
altNameToEnum.put(quality.altImageName + time.altImageNumber, quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -398,7 +478,7 @@ public final class AlternativeThumbnailsPatch {
|
|||
}
|
||||
|
||||
String getAltImageNameToUse() {
|
||||
return altImageName + Settings.ALT_THUMBNAIL_STILLS_TIME.get();
|
||||
return altImageName + Settings.ALT_THUMBNAIL_STILLS_TIME.get().altImageNumber;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,12 +590,11 @@ public final class AlternativeThumbnailsPatch {
|
|||
|
||||
boolean imageFileFound;
|
||||
try {
|
||||
Logger.printDebug(() -> "Verifying image: " + imageUrl);
|
||||
// This hooked code is running on a low priority thread, and it's slightly faster
|
||||
// to run the url connection thru the integrations thread pool which runs at the highest priority.
|
||||
final long start = System.currentTimeMillis();
|
||||
imageFileFound = Utils.submitOnBackgroundThread(() -> {
|
||||
final int connectionTimeoutMillis = 5000;
|
||||
final int connectionTimeoutMillis = 10000; // 10 seconds.
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(imageUrl).openConnection();
|
||||
connection.setConnectTimeout(connectionTimeoutMillis);
|
||||
connection.setReadTimeout(connectionTimeoutMillis);
|
||||
|
@ -533,7 +612,7 @@ public final class AlternativeThumbnailsPatch {
|
|||
}
|
||||
return false;
|
||||
}).get();
|
||||
Logger.printDebug(() -> "Alt verification took: " + (System.currentTimeMillis() - start) + "ms");
|
||||
Logger.printDebug(() -> "Verification took: " + (System.currentTimeMillis() - start) + "ms for image: " + imageUrl);
|
||||
} catch (ExecutionException | InterruptedException ex) {
|
||||
Logger.printInfo(() -> "Could not verify alt url: " + imageUrl, ex);
|
||||
imageFileFound = false;
|
||||
|
@ -597,7 +676,7 @@ public final class AlternativeThumbnailsPatch {
|
|||
? "" : fullUrl.substring(imageExtensionEndIndex);
|
||||
}
|
||||
|
||||
/** @noinspection SameParameterValue*/
|
||||
/** @noinspection SameParameterValue */
|
||||
String createStillsUrl(@NonNull ThumbnailQuality qualityToUse, boolean includeViewTracking) {
|
||||
// Images could be upgraded to webp if they are not already, but this fails quite often,
|
||||
// especially for new videos uploaded in the last hour.
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HideBreakingNewsPatch {
|
||||
|
||||
/**
|
||||
* When spoofing to app versions 17.31.00 and older, the watch history preview bar uses
|
||||
* the same layout components as the breaking news shelf.
|
||||
*
|
||||
* Breaking news does not appear to be present in these older versions anyways.
|
||||
*/
|
||||
private static final boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory =
|
||||
SpoofAppVersionPatch.isSpoofingToLessThan("18.01.00");
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void hideBreakingNews(View view) {
|
||||
if (!Settings.HIDE_BREAKING_NEWS.get()
|
||||
|| isSpoofingOldVersionWithHorizontalCardListWatchHistory) return;
|
||||
Utils.hideViewByLayoutParams(view);
|
||||
}
|
||||
}
|
|
@ -2,7 +2,11 @@ package app.revanced.integrations.youtube.patches;
|
|||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
* Patch is obsolete and will be deleted in a future release
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Deprecated()
|
||||
public class HideEmailAddressPatch {
|
||||
//Used by app.revanced.patches.youtube.layout.personalinformation.patch.HideEmailAddressPatch
|
||||
public static int hideEmailAddress(int originalValue) {
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HideLoadMoreButtonPatch {
|
||||
public static void hideLoadMoreButton(View view){
|
||||
if(!Settings.HIDE_LOAD_MORE_BUTTON.get()) return;
|
||||
Utils.hideViewByLayoutParams(view);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.view.View;
|
||||
|
@ -7,7 +8,7 @@ import android.view.View;
|
|||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import android.widget.TextView;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -40,4 +41,11 @@ public final class NavigationButtonsPatch {
|
|||
tabView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void hideNavigationButtonLabels(TextView navigationLabelsView) {
|
||||
hideViewUnderCondition(Settings.HIDE_NAVIGATION_BUTTON_LABELS, navigationLabelsView);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,12 +176,6 @@ public class ReturnYouTubeDislikePatch {
|
|||
textView.removeTextChangedListener(oldUiTextWatcher);
|
||||
textView.addTextChangedListener(oldUiTextWatcher);
|
||||
|
||||
/**
|
||||
* If the patch is changed to include the dislikes button as a parameter to this method,
|
||||
* then if the button is already selected the dislikes could be adjusted using
|
||||
* {@link ReturnYouTubeDislike#setUserVote(Vote)}
|
||||
*/
|
||||
|
||||
updateOldUIDislikesTextView();
|
||||
|
||||
} catch (Exception ex) {
|
||||
|
@ -241,7 +235,7 @@ public class ReturnYouTubeDislikePatch {
|
|||
true, isRollingNumber);
|
||||
} else if (!isRollingNumber && conversionContextString.contains("|shorts_dislike_button.eml|")) {
|
||||
// Litho Shorts player.
|
||||
if (!Settings.RYD_SHORTS.get()) {
|
||||
if (!Settings.RYD_SHORTS.get() || Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) {
|
||||
// Must clear the current video here, otherwise if the user opens a regular video
|
||||
// then opens a litho short (while keeping the regular video on screen), then closes the short,
|
||||
// the original video may show the incorrect dislike value.
|
||||
|
@ -314,19 +308,25 @@ public class ReturnYouTubeDislikePatch {
|
|||
*/
|
||||
public static float onRollingNumberMeasured(String text, float measuredTextWidth) {
|
||||
try {
|
||||
if (Settings.RYD_ENABLED.get() && !Settings.RYD_COMPACT_LAYOUT.get()) {
|
||||
if (Settings.RYD_ENABLED.get()) {
|
||||
if (ReturnYouTubeDislike.isPreviouslyCreatedSegmentedSpan(text)) {
|
||||
// +1 pixel is needed for some foreign languages that measure
|
||||
// the text different from what is used for layout (Greek in particular).
|
||||
// Probably a bug in Android, but who knows.
|
||||
// Single line mode is also used as an additional fix for this issue.
|
||||
return measuredTextWidth + ReturnYouTubeDislike.leftSeparatorBounds.right
|
||||
+ ReturnYouTubeDislike.leftSeparatorShapePaddingPixels + 1;
|
||||
if (Settings.RYD_COMPACT_LAYOUT.get()) {
|
||||
return measuredTextWidth + 1;
|
||||
}
|
||||
|
||||
return measuredTextWidth + 1
|
||||
+ ReturnYouTubeDislike.leftSeparatorBounds.right
|
||||
+ ReturnYouTubeDislike.leftSeparatorShapePaddingPixels;
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onRollingNumberMeasured failure", ex);
|
||||
}
|
||||
|
||||
return measuredTextWidth;
|
||||
}
|
||||
|
||||
|
@ -344,10 +344,12 @@ public class ReturnYouTubeDislikePatch {
|
|||
} else {
|
||||
view.setCompoundDrawables(separator, null, null, null);
|
||||
}
|
||||
|
||||
// Disliking can cause the span to grow in size, which is ok and is laid out correctly,
|
||||
// but if the user then removes their dislike the layout will not adjust to the new shorter width.
|
||||
// Use a center alignment to take up any extra space.
|
||||
view.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
|
||||
|
||||
// Single line mode does not clip words if the span is larger than the view bounds.
|
||||
// The styled span applied to the view should always have the same bounds,
|
||||
// but use this feature just in case the measurements are somehow off by a few pixels.
|
||||
|
@ -449,7 +451,7 @@ public class ReturnYouTubeDislikePatch {
|
|||
if (!Settings.RYD_ENABLED.get()) {
|
||||
return false;
|
||||
}
|
||||
if (!Settings.RYD_SHORTS.get()) {
|
||||
if (!Settings.RYD_SHORTS.get() || Settings.HIDE_SHORTS_DISLIKE_BUTTON.get()) {
|
||||
// Must clear the data here, in case a new video was loaded while PlayerType
|
||||
// suggested the video was not a short (can happen when spoofing to an old app version).
|
||||
clearData();
|
||||
|
|
|
@ -81,7 +81,7 @@ public final class VideoInformation {
|
|||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String newPlayerResponseSignature(@NonNull String signature, boolean isShortAndOpeningOrPlaying) {
|
||||
public static String newPlayerResponseSignature(@NonNull String signature, String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||
final boolean isShort = playerParametersAreShort(signature);
|
||||
playerResponseVideoIdIsShort = isShort;
|
||||
if (!isShort || isShortAndOpeningOrPlaying) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.text.Html;
|
|||
import android.text.method.LinkMovementMethod;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.patches.announcements.requests.AnnouncementsRoutes;
|
||||
|
@ -60,7 +61,7 @@ public final class AnnouncementsPatch {
|
|||
return;
|
||||
}
|
||||
|
||||
var jsonString = Requester.parseInputStreamAndClose(connection.getInputStream(), false);
|
||||
var jsonString = Requester.parseStringAndDisconnect(connection);
|
||||
|
||||
|
||||
// Parse the announcement. Fall-back to raw string if it fails.
|
||||
|
@ -115,10 +116,10 @@ public final class AnnouncementsPatch {
|
|||
.setTitle(finalTitle)
|
||||
.setMessage(finalMessage)
|
||||
.setIcon(finalLevel.icon)
|
||||
.setPositiveButton("Ok", (dialog, which) -> {
|
||||
.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
Settings.ANNOUNCEMENT_LAST_ID.save(finalId);
|
||||
dialog.dismiss();
|
||||
}).setNegativeButton("Dismiss", (dialog, which) -> {
|
||||
}).setNegativeButton(str("revanced_announcements_dialog_dismiss"), (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setCancelable(false)
|
||||
|
|
|
@ -23,6 +23,10 @@ public final class AdsFilter extends Filter {
|
|||
// endregion
|
||||
|
||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||
|
||||
private final StringFilterGroup channelProfile;
|
||||
private final ByteArrayFilterGroup visitStoreButton;
|
||||
|
||||
private final StringFilterGroup shoppingLinks;
|
||||
|
||||
public AdsFilter() {
|
||||
|
@ -100,6 +104,16 @@ public final class AdsFilter extends Filter {
|
|||
"expandable_list"
|
||||
);
|
||||
|
||||
channelProfile = new StringFilterGroup(
|
||||
null,
|
||||
"channel_profile.eml"
|
||||
);
|
||||
|
||||
visitStoreButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_VISIT_STORE_BUTTON,
|
||||
"header_store_button"
|
||||
);
|
||||
|
||||
final var webLinkPanel = new StringFilterGroup(
|
||||
Settings.HIDE_WEB_SEARCH_RESULTS,
|
||||
"web_link_panel"
|
||||
|
@ -122,6 +136,7 @@ public final class AdsFilter extends Filter {
|
|||
viewProducts,
|
||||
selfSponsor,
|
||||
fullscreenAd,
|
||||
channelProfile,
|
||||
webLinkPanel,
|
||||
shoppingLinks,
|
||||
movieAds
|
||||
|
@ -129,8 +144,8 @@ public final class AdsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (exceptions.matches(path))
|
||||
return false;
|
||||
|
||||
|
@ -140,6 +155,13 @@ public final class AdsFilter extends Filter {
|
|||
return false; // Do not actually filter the fullscreen ad otherwise it will leave a dimmed screen.
|
||||
}
|
||||
|
||||
if (matchedGroup == channelProfile) {
|
||||
if (visitStoreButton.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for the index because of likelihood of false positives.
|
||||
if (matchedGroup == shoppingLinks && contentIndex != 0)
|
||||
return false;
|
||||
|
@ -153,7 +175,7 @@ public final class AdsFilter extends Filter {
|
|||
* @param view The view, which shows ads.
|
||||
*/
|
||||
public static void hideAdAttributionView(View view) {
|
||||
Utils.hideViewBy1dpUnderCondition(Settings.HIDE_GENERAL_ADS, view);
|
||||
Utils.hideViewBy0dpUnderCondition(Settings.HIDE_GENERAL_ADS, view);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
final class ButtonsFilter extends Filter {
|
||||
private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.eml";
|
||||
|
||||
|
@ -26,7 +22,7 @@ final class ButtonsFilter extends Filter {
|
|||
|
||||
bufferFilterPathGroup = new StringFilterGroup(
|
||||
null,
|
||||
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
|
||||
"|ContainerType|button.eml|"
|
||||
);
|
||||
addPathCallbacks(
|
||||
new StringFilterGroup(
|
||||
|
@ -67,10 +63,6 @@ final class ButtonsFilter extends Filter {
|
|||
Settings.HIDE_CLIP_BUTTON,
|
||||
"yt_outline_scissors"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHOP_BUTTON,
|
||||
"yt_outline_bag"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_THANKS_BUTTON,
|
||||
"yt_outline_dollar_sign_heart"
|
||||
|
@ -89,8 +81,8 @@ final class ButtonsFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
// If the current matched group is the action bar group,
|
||||
// in case every filter group is enabled, hide the action bar.
|
||||
if (matchedGroup == actionBarGroup) {
|
||||
|
|
|
@ -1,10 +1,18 @@
|
|||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final class CommentsFilter extends Filter {
|
||||
|
||||
private static final String TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH
|
||||
= "|CellType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|";
|
||||
|
||||
private final StringFilterGroup commentComposer;
|
||||
private final ByteArrayFilterGroup emojiPickerBufferGroup;
|
||||
|
||||
public CommentsFilter() {
|
||||
var comments = new StringFilterGroup(
|
||||
Settings.HIDE_COMMENTS_SECTION,
|
||||
|
@ -19,9 +27,38 @@ final class CommentsFilter extends Filter {
|
|||
"comments_entry_point_simplebox"
|
||||
);
|
||||
|
||||
commentComposer = new StringFilterGroup(
|
||||
Settings.HIDE_COMMENT_TIMESTAMP_AND_EMOJI_BUTTONS,
|
||||
"comment_composer.eml"
|
||||
);
|
||||
|
||||
emojiPickerBufferGroup = new ByteArrayFilterGroup(
|
||||
null,
|
||||
"id.comment.quick_emoji.button"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
comments,
|
||||
previewComment
|
||||
previewComment,
|
||||
commentComposer
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == commentComposer) {
|
||||
// To completely hide the emoji buttons (and leave no empty space), the timestamp button is
|
||||
// also hidden because the buffer is exactly the same and there's no way selectively hide.
|
||||
if (contentIndex == 0
|
||||
&& path.endsWith(TIMESTAMP_OR_EMOJI_BUTTONS_ENDS_WITH_PATH)
|
||||
&& emojiPickerBufferGroup.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
|
@ -42,9 +41,9 @@ final class CustomFilter extends Filter {
|
|||
public static final String SYNTAX_BUFFER_SYMBOL = "$";
|
||||
|
||||
/**
|
||||
* @return the parsed objects, or NULL if there was a parse error.
|
||||
* @return the parsed objects
|
||||
*/
|
||||
@Nullable
|
||||
@NonNull
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
static Collection<CustomFilterGroup> parseCustomFilterGroups() {
|
||||
String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get();
|
||||
|
@ -147,8 +146,8 @@ final class CustomFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
// All callbacks are custom filter groups.
|
||||
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
|
||||
if (custom.startsWith && contentIndex != 0) {
|
||||
|
|
|
@ -45,7 +45,7 @@ final class DescriptionComponentsFilter extends Filter {
|
|||
);
|
||||
|
||||
final StringFilterGroup transcriptSection = new StringFilterGroup(
|
||||
Settings.HIDE_TRANSCIPT_SECTION,
|
||||
Settings.HIDE_TRANSCRIPT_SECTION,
|
||||
"transcript_section"
|
||||
);
|
||||
|
||||
|
|
|
@ -100,7 +100,8 @@ final class KeywordContentFilter extends Filter {
|
|||
private final StringFilterGroup containsFilter = new StringFilterGroup(
|
||||
null,
|
||||
"modern_type_shelf_header_content.eml",
|
||||
"shorts_lockup_cell.eml" // Part of 'shorts_shelf_carousel.eml'
|
||||
"shorts_lockup_cell.eml", // Part of 'shorts_shelf_carousel.eml'
|
||||
"video_card.eml" // Shorts that appear in a horizontal shelf.
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -112,34 +113,36 @@ final class KeywordContentFilter extends Filter {
|
|||
|
||||
private volatile ByteTrieSearch bufferSearch;
|
||||
|
||||
private static void logNavigationState(String state) {
|
||||
// Enable locally to debug filtering. Default off to reduce log spam.
|
||||
final boolean LOG_NAVIGATION_STATE = false;
|
||||
// noinspection ConstantValue
|
||||
if (LOG_NAVIGATION_STATE) {
|
||||
Logger.printDebug(() -> "Navigation state: " + state);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hideKeywordSettingIsActive() {
|
||||
if (NavigationBar.isSearchBarActive()) {
|
||||
// Must check first. Search bar can be active with almost any tab.
|
||||
logNavigationState("Search");
|
||||
return Settings.HIDE_KEYWORD_CONTENT_SEARCH.get();
|
||||
} else if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||
// Must check player type first, as search bar can be active behind the player.
|
||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||
// For now, consider the under video results the same as the home feed.
|
||||
logNavigationState("Player active");
|
||||
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||
} else if (NavigationButton.HOME.isSelected()) {
|
||||
logNavigationState("Home tab");
|
||||
return Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||
} else if (NavigationButton.SUBSCRIPTIONS.isSelected()) {
|
||||
logNavigationState("Subscription tab");
|
||||
return Settings.HIDE_SUBSCRIPTIONS_BUTTON.get();
|
||||
} else {
|
||||
// User is in the Library or Notifications tab.
|
||||
logNavigationState("Ignored tab");
|
||||
}
|
||||
|
||||
// Must check second, as search can be from any tab.
|
||||
if (NavigationBar.isSearchBarActive()) {
|
||||
return Settings.HIDE_KEYWORD_CONTENT_SEARCH.get();
|
||||
}
|
||||
|
||||
// Avoid checking navigation button status if all other settings are off.
|
||||
final boolean hideHome = Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||
final boolean hideSubscriptions = Settings.HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS.get();
|
||||
if (!hideHome && !hideSubscriptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||
if (selectedNavButton == null) {
|
||||
return hideHome; // Unknown tab, treat the same as home.
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.HOME) {
|
||||
return hideHome;
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
||||
return hideSubscriptions;
|
||||
}
|
||||
// User is in the Library or Notifications tab.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -151,7 +154,7 @@ final class KeywordContentFilter extends Filter {
|
|||
return sentence;
|
||||
}
|
||||
final int firstCodePoint = sentence.codePointAt(0);
|
||||
// In some non English languages title case is different than upper case.
|
||||
// In some non English languages title case is different than uppercase.
|
||||
return new StringBuilder()
|
||||
.appendCodePoint(Character.toTitleCase(firstCodePoint))
|
||||
.append(sentence, Character.charCount(firstCodePoint), sentence.length())
|
||||
|
@ -165,6 +168,7 @@ final class KeywordContentFilter extends Filter {
|
|||
if (sentence.isEmpty()) {
|
||||
return sentence;
|
||||
}
|
||||
|
||||
final int delimiter = ' ';
|
||||
// Use code points and not characters to handle unicode surrogates.
|
||||
int[] codePoints = sentence.codePoints().toArray();
|
||||
|
@ -195,6 +199,7 @@ final class KeywordContentFilter extends Filter {
|
|||
|
||||
private synchronized void parseKeywords() { // Must be synchronized since Litho is multi-threaded.
|
||||
String rawKeywords = Settings.HIDE_KEYWORD_CONTENT_PHRASES.get();
|
||||
//noinspection StringEquality
|
||||
if (rawKeywords == lastKeywordPhrasesParsed) {
|
||||
Logger.printDebug(() -> "Using previously initialized search");
|
||||
return; // Another thread won the race, and search is already initialized.
|
||||
|
@ -256,8 +261,8 @@ final class KeywordContentFilter extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
||||
return false;
|
||||
}
|
||||
|
@ -265,6 +270,7 @@ final class KeywordContentFilter extends Filter {
|
|||
if (!hideKeywordSettingIsActive()) return false;
|
||||
|
||||
// Field is intentionally compared using reference equality.
|
||||
//noinspection StringEquality
|
||||
if (Settings.HIDE_KEYWORD_CONTENT_PHRASES.get() != lastKeywordPhrasesParsed) {
|
||||
// User changed the keywords.
|
||||
parseKeywords();
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.StringTrieSearch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public final class LayoutComponentsFilter extends Filter {
|
||||
private final StringTrieSearch exceptions = new StringTrieSearch();
|
||||
private static final StringTrieSearch mixPlaylistsExceptions = new StringTrieSearch();
|
||||
|
@ -32,6 +37,7 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
private final StringFilterGroup compactChannelBarInner;
|
||||
private final StringFilterGroup compactChannelBarInnerButton;
|
||||
private final ByteArrayFilterGroup joinMembershipButton;
|
||||
private final StringFilterGroup horizontalShelves;
|
||||
|
||||
static {
|
||||
mixPlaylistsExceptions.addPatterns(
|
||||
|
@ -40,7 +46,6 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public LayoutComponentsFilter() {
|
||||
exceptions.addPatterns(
|
||||
|
@ -108,8 +113,8 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
"medical_panel"
|
||||
);
|
||||
|
||||
final var paidContent = new StringFilterGroup(
|
||||
Settings.HIDE_PAID_CONTENT,
|
||||
final var paidPromotion = new StringFilterGroup(
|
||||
Settings.HIDE_PAID_PROMOTION_LABEL,
|
||||
"paid_content_overlay"
|
||||
);
|
||||
|
||||
|
@ -172,6 +177,11 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
"fullscreen_related_videos"
|
||||
);
|
||||
|
||||
final var playables = new StringFilterGroup(
|
||||
Settings.HIDE_PLAYABLES,
|
||||
"horizontal_gaming_shelf.eml"
|
||||
);
|
||||
|
||||
final var quickActions = new StringFilterGroup(
|
||||
Settings.HIDE_QUICK_ACTIONS,
|
||||
"quick_actions"
|
||||
|
@ -234,17 +244,25 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
"endorsement_header_footer"
|
||||
);
|
||||
|
||||
horizontalShelves = new StringFilterGroup(
|
||||
Settings.HIDE_HORIZONTAL_SHELVES,
|
||||
"horizontal_video_shelf.eml",
|
||||
"horizontal_shelf.eml",
|
||||
"horizontal_tile_shelf.eml"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
expandableMetadata,
|
||||
inFeedSurvey,
|
||||
notifyMe,
|
||||
channelBar,
|
||||
communityPosts,
|
||||
paidContent,
|
||||
paidPromotion,
|
||||
searchResultVideo,
|
||||
latestPosts,
|
||||
channelWatermark,
|
||||
communityGuidelines,
|
||||
playables,
|
||||
quickActions,
|
||||
relatedVideos,
|
||||
compactBanner,
|
||||
|
@ -260,23 +278,27 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
timedReactions,
|
||||
imageShelf,
|
||||
channelMemberShelf,
|
||||
forYouShelf
|
||||
forYouShelf,
|
||||
horizontalShelves
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (matchedGroup == searchResultVideo) {
|
||||
if (searchResultRecommendations.check(protobufBufferArray).isFiltered()) {
|
||||
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)
|
||||
{
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
if (exceptions.matches(path)) return false; // Exceptions are not filtered.
|
||||
|
||||
|
@ -295,6 +317,14 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
// TODO: This also hides the feed Shorts shelf header
|
||||
if (matchedGroup == searchResultShelfHeader && contentIndex != 0) return false;
|
||||
|
||||
if (matchedGroup == horizontalShelves) {
|
||||
if (contentIndex == 0 && hideShelves()) {
|
||||
return super.isFiltered(path, identifier, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
|
@ -323,7 +353,40 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean showWatermark() {
|
||||
return !Settings.HIDE_VIDEO_CHANNEL_WATERMARK.get();
|
||||
}
|
||||
|
||||
private static final boolean HIDE_SHOW_MORE_BUTTON_ENABLED = Settings.HIDE_SHOW_MORE_BUTTON.get();
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void hideShowMoreButton(View view) {
|
||||
if (HIDE_SHOW_MORE_BUTTON_ENABLED
|
||||
&& NavigationBar.isSearchBarActive()
|
||||
// Search bar can be active but behind the player.
|
||||
&& !PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||
Utils.hideViewByLayoutParams(view);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hideShelves() {
|
||||
// If the player is opened while library is selected,
|
||||
// then filter any recommendations below the player.
|
||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||
// Or if the search is active while library is selected, then also filter.
|
||||
|| NavigationBar.isSearchBarActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check navigation button last.
|
||||
// Only filter if the library tab is not selected.
|
||||
// This check is important as the shelf layout is used for the library tab playlists.
|
||||
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||
return selectedNavButton != null && !selectedNavButton.isLibraryOrYouTab();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -383,7 +383,6 @@ abstract class Filter {
|
|||
*/
|
||||
final class DummyFilter extends Filter { }
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@SuppressWarnings("unused")
|
||||
public final class LithoFilterPatch {
|
||||
/**
|
||||
|
|
|
@ -45,10 +45,6 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||
Settings.HIDE_AMBIENT_MODE_MENU,
|
||||
"yt_outline_screen_light"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_REPORT_MENU,
|
||||
"yt_outline_flag"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_HELP_MENU,
|
||||
"yt_outline_question_circle"
|
||||
|
@ -57,6 +53,10 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||
Settings.HIDE_MORE_INFO_MENU,
|
||||
"yt_outline_info_circle"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_LOCK_SCREEN_MENU,
|
||||
"yt_outline_lock"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SPEED_MENU,
|
||||
"yt_outline_play_arrow_half_circle"
|
||||
|
@ -75,15 +75,21 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
|
|||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
// Shorts also use this player flyout panel
|
||||
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered())
|
||||
return false;
|
||||
|
||||
// Only 1 path callback was added, so the matched group must be the overflow menu.
|
||||
if (contentIndex == 0 && flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
if (contentIndex != 0) {
|
||||
return false; // Overflow menu is always the start of the path.
|
||||
}
|
||||
|
||||
// Shorts also use this player flyout panel
|
||||
if (PlayerType.getCurrent().isNoneOrHidden() || exception.check(protobufBufferArray).isFiltered()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (flyoutFilterGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
// Super class handles logging.
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -29,7 +26,6 @@ import app.revanced.integrations.youtube.TrieSearch;
|
|||
*
|
||||
* Once a way to asynchronously update litho text is found, this strategy will no longer be needed.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
||||
|
||||
/**
|
||||
|
@ -53,6 +49,7 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
|||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static void newPlayerResponseVideoId(String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||
try {
|
||||
if (!isShortAndOpeningOrPlaying || !Settings.RYD_SHORTS.get()) {
|
||||
|
@ -84,8 +81,8 @@ public final class ReturnYouTubeDislikeFilterPatch extends Filter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(protobufBufferArray);
|
||||
if (result.isFiltered()) {
|
||||
String matchedVideoId = findVideoId(protobufBufferArray);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
||||
import static app.revanced.integrations.shared.Utils.removeViewFromParentUnderConditions;
|
||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
||||
|
||||
|
@ -16,32 +16,35 @@ import app.revanced.integrations.youtube.shared.NavigationBar;
|
|||
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public final class ShortsFilter extends Filter {
|
||||
public static PivotBar pivotBar; // Set by patch.
|
||||
private final String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.eml";
|
||||
|
||||
private final static String REEL_CHANNEL_BAR_PATH = "reel_channel_bar.eml";
|
||||
/**
|
||||
* For paid promotion label and subscribe button that appears in the channel bar.
|
||||
*/
|
||||
private final static String REEL_METAPANEL_PATH = "reel_metapanel.eml";
|
||||
|
||||
private final StringFilterGroup shortsCompactFeedVideoPath;
|
||||
private final ByteArrayFilterGroup shortsCompactFeedVideoBuffer;
|
||||
|
||||
private final StringFilterGroup channelBar;
|
||||
private final StringFilterGroup fullVideoLinkLabel;
|
||||
private final StringFilterGroup videoTitle;
|
||||
private final StringFilterGroup reelSoundMetadata;
|
||||
private final StringFilterGroup subscribeButton;
|
||||
private final StringFilterGroup subscribeButtonPaused;
|
||||
private final StringFilterGroup soundButton;
|
||||
private final StringFilterGroup infoPanel;
|
||||
private final StringFilterGroup joinButton;
|
||||
private final StringFilterGroup paidPromotionButton;
|
||||
private final StringFilterGroup shelfHeader;
|
||||
|
||||
private final StringFilterGroup suggestedAction;
|
||||
private final ByteArrayFilterGroupList suggestedActionsGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
private final StringFilterGroup actionBar;
|
||||
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
public ShortsFilter() {
|
||||
//
|
||||
// Identifier components.
|
||||
//
|
||||
|
||||
var shorts = new StringFilterGroup(
|
||||
var shortsIdentifiers = new StringFilterGroup(
|
||||
null, // Setting is based on navigation state.
|
||||
"shorts_shelf",
|
||||
"inline_shorts",
|
||||
|
@ -49,6 +52,7 @@ public final class ShortsFilter extends Filter {
|
|||
"shorts_video_cell",
|
||||
"shorts_pivot_item"
|
||||
);
|
||||
|
||||
// Feed Shorts shelf header.
|
||||
// Use a different filter group for this pattern, as it requires an additional check after matching.
|
||||
shelfHeader = new StringFilterGroup(
|
||||
|
@ -56,24 +60,59 @@ public final class ShortsFilter extends Filter {
|
|||
"shelf_header.eml"
|
||||
);
|
||||
|
||||
// Home / subscription feed components.
|
||||
var thanksButton = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_THANKS_BUTTON,
|
||||
"suggested_action"
|
||||
);
|
||||
|
||||
addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
|
||||
addIdentifierCallbacks(shortsIdentifiers, shelfHeader);
|
||||
|
||||
//
|
||||
// Path components.
|
||||
//
|
||||
|
||||
shortsCompactFeedVideoPath = new StringFilterGroup(null,
|
||||
// Shorts that appear in the feed/search when the device is using tablet layout.
|
||||
"compact_video.eml",
|
||||
// Search results that appear in a horizontal shelf.
|
||||
"video_card.eml");
|
||||
|
||||
// Shorts that appear in the feed/search when the device is using tablet layout.
|
||||
shortsCompactFeedVideoPath = new StringFilterGroup(null, "compact_video.eml");
|
||||
// Filter out items that use the 'frame0' thumbnail.
|
||||
// This is a valid thumbnail for both regular videos and Shorts,
|
||||
// but it appears these thumbnails are used only for Shorts.
|
||||
shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(null, "/frame0.jpg");
|
||||
|
||||
// Shorts player components.
|
||||
StringFilterGroup pausedOverlayButtons = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_PAUSED_OVERLAY_BUTTONS,
|
||||
"shorts_paused_state"
|
||||
);
|
||||
|
||||
StringFilterGroup channelBar = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_CHANNEL_BAR,
|
||||
REEL_CHANNEL_BAR_PATH
|
||||
);
|
||||
|
||||
StringFilterGroup fullVideoLinkLabel = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_FULL_VIDEO_LINK_LABEL,
|
||||
"reel_multi_format_link"
|
||||
);
|
||||
|
||||
StringFilterGroup videoTitle = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_VIDEO_TITLE,
|
||||
"shorts_video_title_item"
|
||||
);
|
||||
|
||||
StringFilterGroup reelSoundMetadata = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_SOUND_METADATA_LABEL,
|
||||
"reel_sound_metadata"
|
||||
);
|
||||
|
||||
StringFilterGroup soundButton = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_SOUND_BUTTON,
|
||||
"reel_pivot_button"
|
||||
);
|
||||
|
||||
StringFilterGroup infoPanel = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_INFO_PANEL,
|
||||
"shorts_info_panel_overview"
|
||||
);
|
||||
|
||||
joinButton = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_JOIN_BUTTON,
|
||||
"sponsor_button"
|
||||
|
@ -84,39 +123,9 @@ public final class ShortsFilter extends Filter {
|
|||
"subscribe_button"
|
||||
);
|
||||
|
||||
subscribeButtonPaused = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED,
|
||||
"shorts_paused_state"
|
||||
);
|
||||
|
||||
channelBar = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_CHANNEL_BAR,
|
||||
REEL_CHANNEL_BAR_PATH
|
||||
);
|
||||
|
||||
fullVideoLinkLabel = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_FULL_VIDEO_LINK_LABEL,
|
||||
"reel_multi_format_link"
|
||||
);
|
||||
|
||||
videoTitle = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_VIDEO_TITLE,
|
||||
"shorts_video_title_item"
|
||||
);
|
||||
|
||||
reelSoundMetadata = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_SOUND_METADATA_LABEL,
|
||||
"reel_sound_metadata"
|
||||
);
|
||||
|
||||
soundButton = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_SOUND_BUTTON,
|
||||
"reel_pivot_button"
|
||||
);
|
||||
|
||||
infoPanel = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_INFO_PANEL,
|
||||
"shorts_info_panel_overview"
|
||||
paidPromotionButton = new StringFilterGroup(
|
||||
Settings.HIDE_PAID_PROMOTION_LABEL,
|
||||
"reel_player_disclosure.eml"
|
||||
);
|
||||
|
||||
actionBar = new StringFilterGroup(
|
||||
|
@ -124,44 +133,77 @@ public final class ShortsFilter extends Filter {
|
|||
"shorts_action_bar"
|
||||
);
|
||||
|
||||
suggestedAction = new StringFilterGroup(
|
||||
null,
|
||||
"suggested_action.eml"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
shortsCompactFeedVideoPath,
|
||||
joinButton, subscribeButton, subscribeButtonPaused,
|
||||
channelBar, fullVideoLinkLabel, videoTitle, reelSoundMetadata,
|
||||
soundButton, infoPanel, actionBar
|
||||
);
|
||||
|
||||
var shortsLikeButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_LIKE_BUTTON,
|
||||
"shorts_like_button"
|
||||
);
|
||||
|
||||
var shortsDislikeButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_DISLIKE_BUTTON,
|
||||
"shorts_dislike_button"
|
||||
);
|
||||
|
||||
var shortsCommentButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
|
||||
"reel_comment_button"
|
||||
);
|
||||
|
||||
var shortsShareButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_SHARE_BUTTON,
|
||||
"reel_share_button"
|
||||
);
|
||||
|
||||
var shortsRemixButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_REMIX_BUTTON,
|
||||
"reel_remix_button"
|
||||
shortsCompactFeedVideoPath, suggestedAction, actionBar, joinButton, subscribeButton,
|
||||
paidPromotionButton, pausedOverlayButtons, channelBar, fullVideoLinkLabel, videoTitle,
|
||||
reelSoundMetadata, soundButton, infoPanel
|
||||
);
|
||||
|
||||
//
|
||||
// Action buttons
|
||||
//
|
||||
videoActionButtonGroupList.addAll(
|
||||
shortsLikeButton,
|
||||
shortsDislikeButton,
|
||||
shortsCommentButton,
|
||||
shortsShareButton,
|
||||
shortsRemixButton
|
||||
// This also appears as the path item 'shorts_like_button.eml'
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_LIKE_BUTTON,
|
||||
"reel_like_button",
|
||||
"reel_like_toggled_button"
|
||||
),
|
||||
// This also appears as the path item 'shorts_dislike_button.eml'
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_DISLIKE_BUTTON,
|
||||
"reel_dislike_button",
|
||||
"reel_dislike_toggled_button"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
|
||||
"reel_comment_button"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_SHARE_BUTTON,
|
||||
"reel_share_button"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_REMIX_BUTTON,
|
||||
"reel_remix_button"
|
||||
)
|
||||
);
|
||||
|
||||
//
|
||||
// Suggested actions.
|
||||
//
|
||||
suggestedActionsGroupList.addAll(
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_SHOP_BUTTON,
|
||||
"yt_outline_bag_"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_TAGGED_PRODUCTS,
|
||||
// Product buttons show pictures of the products, and does not have any unique icons to identify.
|
||||
// Instead use a unique identifier found in the buffer.
|
||||
"PAproduct_listZ"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_LOCATION_LABEL,
|
||||
"yt_outline_location_point_"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_SAVE_SOUND_BUTTON,
|
||||
"yt_outline_list_add_"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_SEARCH_SUGGESTIONS,
|
||||
"yt_outline_search_"
|
||||
),
|
||||
new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_SUPER_THANKS_BUTTON,
|
||||
"yt_outline_dollar_sign_heart_"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -169,19 +211,18 @@ public final class ShortsFilter extends Filter {
|
|||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (contentType == FilterContentType.PATH) {
|
||||
// Always filter if matched.
|
||||
if (matchedGroup == soundButton ||
|
||||
matchedGroup == infoPanel ||
|
||||
matchedGroup == channelBar ||
|
||||
matchedGroup == fullVideoLinkLabel ||
|
||||
matchedGroup == videoTitle ||
|
||||
matchedGroup == reelSoundMetadata ||
|
||||
matchedGroup == subscribeButtonPaused
|
||||
) return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) {
|
||||
// Selectively filter to avoid false positive filtering of other subscribe/join buttons.
|
||||
if (path.startsWith(REEL_CHANNEL_BAR_PATH) || path.startsWith(REEL_METAPANEL_PATH)) {
|
||||
return super.isFiltered(
|
||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (matchedGroup == shortsCompactFeedVideoPath) {
|
||||
if (shouldHideShortsFeedItems() && contentIndex == 0
|
||||
&& shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
|
||||
if (shouldHideShortsFeedItems() && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
|
@ -189,25 +230,26 @@ public final class ShortsFilter extends Filter {
|
|||
|
||||
// Video action buttons (like, dislike, comment, share, remix) have the same path.
|
||||
if (matchedGroup == actionBar) {
|
||||
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
|
||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||
);
|
||||
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(
|
||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible
|
||||
// to avoid false positives.
|
||||
if (matchedGroup == subscribeButton ||
|
||||
matchedGroup == joinButton
|
||||
) {
|
||||
if (path.startsWith(REEL_CHANNEL_BAR_PATH)) return super.isFiltered(
|
||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||
); // else, return false.
|
||||
if (matchedGroup == suggestedAction) {
|
||||
// Suggested actions can be at the start or in the middle of a path.
|
||||
if (suggestedActionsGroupList.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(
|
||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// Feed/search path components.
|
||||
// Feed/search identifier components.
|
||||
if (matchedGroup == shelfHeader) {
|
||||
// Because the header is used in watch history and possibly other places, check for the index,
|
||||
// which is 0 when the shelf header is used for Shorts.
|
||||
|
@ -222,14 +264,49 @@ public final class ShortsFilter extends Filter {
|
|||
}
|
||||
|
||||
private static boolean shouldHideShortsFeedItems() {
|
||||
if (NavigationBar.isSearchBarActive()) { // Must check search first.
|
||||
return Settings.HIDE_SHORTS_SEARCH.get();
|
||||
} else if (PlayerType.getCurrent().isMaximizedOrFullscreen()
|
||||
|| NavigationBar.NavigationButton.HOME.isSelected()) {
|
||||
return Settings.HIDE_SHORTS_HOME.get();
|
||||
} else if (NavigationBar.NavigationButton.SUBSCRIPTIONS.isSelected()) {
|
||||
return Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
||||
final boolean hideHome = Settings.HIDE_SHORTS_HOME.get();
|
||||
final boolean hideSubscriptions = Settings.HIDE_SHORTS_SUBSCRIPTIONS.get();
|
||||
final boolean hideSearch = Settings.HIDE_SHORTS_SEARCH.get();
|
||||
|
||||
if (hideHome && hideSubscriptions && hideSearch) {
|
||||
// Shorts suggestions can load in the background if a video is opened and
|
||||
// then immediately minimized before any suggestions are loaded.
|
||||
// In this state the player type will show minimized, which makes it not possible to
|
||||
// distinguish between Shorts suggestions loading in the player and between
|
||||
// scrolling thru search/home/subscription tabs while a player is minimized.
|
||||
//
|
||||
// To avoid this situation for users that never want to show Shorts (all hide Shorts options are enabled)
|
||||
// then hide all Shorts everywhere including the Library history and Library playlists.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Must check player type first, as search bar can be active behind the player.
|
||||
if (PlayerType.getCurrent().isMaximizedOrFullscreen()) {
|
||||
// For now, consider the under video results the same as the home feed.
|
||||
return hideHome;
|
||||
}
|
||||
|
||||
// Must check second, as search can be from any tab.
|
||||
if (NavigationBar.isSearchBarActive()) {
|
||||
return hideSearch;
|
||||
}
|
||||
|
||||
// Avoid checking navigation button status if all other Shorts should show.
|
||||
if (!hideHome && !hideSubscriptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NavigationButton selectedNavButton = NavigationButton.getSelectedNavigationButton();
|
||||
if (selectedNavButton == null) {
|
||||
return hideHome; // Unknown tab, treat the same as home.
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.HOME) {
|
||||
return hideHome;
|
||||
}
|
||||
if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) {
|
||||
return hideSubscriptions;
|
||||
}
|
||||
// User must be in the library tab. Don't hide the history or any playlists here.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -241,6 +318,21 @@ public final class ShortsFilter extends Filter {
|
|||
|
||||
// region Hide the buttons in older versions of YouTube. New versions use Litho.
|
||||
|
||||
public static void hideLikeButton(final View likeButtonView) {
|
||||
// Cannot set the visibility to gone for like/dislike,
|
||||
// as some other unknown YT code also sets the visibility after this hook.
|
||||
//
|
||||
// Setting the view to 0dp works, but that leaves a blank space where
|
||||
// the button was (only relevant for dislikes button).
|
||||
//
|
||||
// Instead remove the view from the parent.
|
||||
removeViewFromParentUnderConditions(Settings.HIDE_SHORTS_LIKE_BUTTON, likeButtonView);
|
||||
}
|
||||
|
||||
public static void hideDislikeButton(final View dislikeButtonView) {
|
||||
removeViewFromParentUnderConditions(Settings.HIDE_SHORTS_DISLIKE_BUTTON, dislikeButtonView);
|
||||
}
|
||||
|
||||
public static void hideShortsCommentsButton(final View commentsButtonView) {
|
||||
hideViewUnderCondition(Settings.HIDE_SHORTS_COMMENTS_BUTTON, commentsButtonView);
|
||||
}
|
||||
|
|
|
@ -5,9 +5,9 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.ListView;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.youtube.patches.components.VideoQualityMenuFilterPatch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
|
||||
/**
|
||||
* This patch contains the logic to show the old video quality menu.
|
||||
|
@ -44,7 +44,18 @@ public final class RestoreOldVideoQualityMenuPatch {
|
|||
}
|
||||
|
||||
/**
|
||||
* Injection point. Only used if spoofing to an old app version.
|
||||
* Injection point.
|
||||
*
|
||||
* Used to force the creation of the advanced menu item for the Shorts quality flyout.
|
||||
*/
|
||||
public static boolean forceAdvancedVideoQualityMenuCreation() {
|
||||
return Settings.RESTORE_OLD_VIDEO_QUALITY_MENU.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* 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;
|
||||
|
@ -52,17 +63,21 @@ public final class RestoreOldVideoQualityMenuPatch {
|
|||
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
|
||||
@Override
|
||||
public void onChildViewAdded(View parent, View child) {
|
||||
Logger.printDebug(() -> "Added listener to old type of quality menu");
|
||||
try {
|
||||
parent.setVisibility(View.GONE);
|
||||
|
||||
parent.setVisibility(View.GONE);
|
||||
final var indexOfAdvancedQualityMenuItem = 4;
|
||||
if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return;
|
||||
|
||||
final var indexOfAdvancedQualityMenuItem = 4;
|
||||
if (listView.indexOfChild(child) != indexOfAdvancedQualityMenuItem) return;
|
||||
Logger.printDebug(() -> "Found advanced menu item in old type of quality menu");
|
||||
|
||||
Logger.printDebug(() -> "Found advanced menu item in old type of quality menu");
|
||||
listView.setSoundEffectsEnabled(false);
|
||||
final var qualityItemMenuPosition = 4;
|
||||
listView.performItemClick(null, qualityItemMenuPosition, 0);
|
||||
|
||||
final var qualityItemMenuPosition = 4;
|
||||
listView.performItemClick(null, qualityItemMenuPosition, 0);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "showOldVideoQualityMenu failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
package app.revanced.integrations.youtube.patches.spoof;
|
||||
|
||||
import static app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
|
||||
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.patches.VideoInformation;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class SpoofClientPatch {
|
||||
private static final boolean SPOOF_CLIENT_ENABLED = Settings.SPOOF_CLIENT.get();
|
||||
private static final boolean SPOOF_CLIENT_USE_IOS = Settings.SPOOF_CLIENT_USE_IOS.get();
|
||||
private static final boolean SPOOF_CLIENT_STORYBOARD = SPOOF_CLIENT_ENABLED && !SPOOF_CLIENT_USE_IOS;
|
||||
|
||||
/**
|
||||
* Any unreachable ip address. Used to intentionally fail requests.
|
||||
*/
|
||||
private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0";
|
||||
private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING);
|
||||
|
||||
@Nullable
|
||||
private static volatile Future<StoryboardRenderer> lastStoryboardFetched;
|
||||
|
||||
private static final Map<String, Future<StoryboardRenderer>> storyboardCache =
|
||||
Collections.synchronizedMap(new LinkedHashMap<>(100) {
|
||||
private static final int CACHE_LIMIT = 100;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Entry eldest) {
|
||||
return size() > CACHE_LIMIT; // Evict the oldest entry if over the cache limit.
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Blocks /get_watch requests by returning a localhost URI.
|
||||
*
|
||||
* @param playerRequestUri The URI of the player request.
|
||||
* @return Localhost URI if the request is a /get_watch request, otherwise the original URI.
|
||||
*/
|
||||
public static Uri blockGetWatchRequest(Uri playerRequestUri) {
|
||||
if (SPOOF_CLIENT_ENABLED) {
|
||||
try {
|
||||
String path = playerRequestUri.getPath();
|
||||
|
||||
if (path != null && path.contains("get_watch")) {
|
||||
Logger.printDebug(() -> "Blocking: " + playerRequestUri + " by returning: " + UNREACHABLE_HOST_URI_STRING);
|
||||
|
||||
return UNREACHABLE_HOST_URI;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "blockGetWatchRequest failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return playerRequestUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Blocks /initplayback requests.
|
||||
* For iOS, an unreachable host URL can be used, but for Android Testsuite, this is not possible.
|
||||
*/
|
||||
public static String blockInitPlaybackRequest(String originalUrlString) {
|
||||
if (SPOOF_CLIENT_ENABLED) {
|
||||
try {
|
||||
var originalUri = Uri.parse(originalUrlString);
|
||||
String path = originalUri.getPath();
|
||||
|
||||
if (path != null && path.contains("initplayback")) {
|
||||
String replacementUriString = (getSpoofClientType() == ClientType.IOS)
|
||||
? UNREACHABLE_HOST_URI_STRING
|
||||
// TODO: Ideally, a local proxy could be setup and block
|
||||
// the request the same way as Burp Suite is capable of
|
||||
// because that way the request is never sent to YouTube unnecessarily.
|
||||
// Just using localhost unfortunately does not work.
|
||||
: originalUri.buildUpon().clearQuery().build().toString();
|
||||
|
||||
Logger.printDebug(() -> "Blocking: " + originalUrlString + " by returning: " + replacementUriString);
|
||||
|
||||
return replacementUriString;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "blockInitPlaybackRequest failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return originalUrlString;
|
||||
}
|
||||
|
||||
private static ClientType getSpoofClientType() {
|
||||
if (SPOOF_CLIENT_USE_IOS) {
|
||||
return ClientType.IOS;
|
||||
}
|
||||
|
||||
StoryboardRenderer renderer = getRenderer(false);
|
||||
if (renderer == null) {
|
||||
// Video is private or otherwise not available.
|
||||
// Test client still works for video playback, but seekbar thumbnails are not available.
|
||||
// Use iOS client instead.
|
||||
Logger.printDebug(() -> "Using iOS client for paid or otherwise restricted video");
|
||||
return ClientType.IOS;
|
||||
}
|
||||
|
||||
if (renderer.isLiveStream) {
|
||||
// Test client does not support live streams.
|
||||
// Use the storyboard renderer information to fallback to iOS if a live stream is opened.
|
||||
Logger.printDebug(() -> "Using iOS client for livestream: " + renderer.videoId);
|
||||
return ClientType.IOS;
|
||||
}
|
||||
|
||||
return ClientType.ANDROID_TESTSUITE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getClientTypeId(int originalClientTypeId) {
|
||||
if (SPOOF_CLIENT_ENABLED) {
|
||||
return getSpoofClientType().id;
|
||||
}
|
||||
|
||||
return originalClientTypeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String getClientVersion(String originalClientVersion) {
|
||||
if (SPOOF_CLIENT_ENABLED) {
|
||||
return getSpoofClientType().version;
|
||||
}
|
||||
|
||||
return originalClientVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean isClientSpoofingEnabled() {
|
||||
return SPOOF_CLIENT_ENABLED;
|
||||
}
|
||||
|
||||
//
|
||||
// Storyboard.
|
||||
//
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static String setPlayerResponseVideoId(String parameters, String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||
if (SPOOF_CLIENT_STORYBOARD) {
|
||||
try {
|
||||
// VideoInformation is not a dependent patch, and only this single helper method is used.
|
||||
// Hook can be called when scrolling thru the feed and a Shorts shelf is present.
|
||||
// Ignore these videos.
|
||||
if (!isShortAndOpeningOrPlaying && VideoInformation.playerParametersAreShort(parameters)) {
|
||||
Logger.printDebug(() -> "Ignoring Short: " + videoId);
|
||||
return parameters;
|
||||
}
|
||||
|
||||
Future<StoryboardRenderer> storyboard = storyboardCache.get(videoId);
|
||||
if (storyboard == null) {
|
||||
storyboard = Utils.submitOnBackgroundThread(() -> getStoryboardRenderer(videoId));
|
||||
storyboardCache.put(videoId, storyboard);
|
||||
lastStoryboardFetched = storyboard;
|
||||
|
||||
// Block until the renderer fetch completes.
|
||||
// This is desired because if this returns without finishing the fetch
|
||||
// then video will start playback but the storyboard is not ready yet.
|
||||
getRenderer(true);
|
||||
} else {
|
||||
lastStoryboardFetched = storyboard;
|
||||
// No need to block on the fetch since it previously loaded.
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "setPlayerResponseVideoId failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
return parameters; // Return the original value since we are observing and not modifying.
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static StoryboardRenderer getRenderer(boolean waitForCompletion) {
|
||||
var future = lastStoryboardFetched;
|
||||
if (future != null) {
|
||||
try {
|
||||
if (waitForCompletion || future.isDone()) {
|
||||
return future.get(20000, TimeUnit.MILLISECONDS); // Any arbitrarily large timeout.
|
||||
} // else, return null.
|
||||
} catch (TimeoutException ex) {
|
||||
Logger.printDebug(() -> "Could not get renderer (get timed out)");
|
||||
} catch (ExecutionException | InterruptedException ex) {
|
||||
// Should never happen.
|
||||
Logger.printException(() -> "Could not get renderer", ex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Called from background threads and from the main thread.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) {
|
||||
if (SPOOF_CLIENT_STORYBOARD) {
|
||||
StoryboardRenderer renderer = getRenderer(false);
|
||||
|
||||
if (renderer != null) {
|
||||
if (!renderer.isLiveStream && renderer.spec != null) {
|
||||
return renderer.spec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return originalStoryboardRendererSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static int getRecommendedLevel(int originalLevel) {
|
||||
if (SPOOF_CLIENT_STORYBOARD) {
|
||||
StoryboardRenderer renderer = getRenderer(false);
|
||||
|
||||
if (renderer != null) {
|
||||
if (!renderer.isLiveStream && renderer.recommendedLevel != null) {
|
||||
return renderer.recommendedLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return originalLevel;
|
||||
}
|
||||
|
||||
private enum ClientType {
|
||||
ANDROID_TESTSUITE(30, "1.9"),
|
||||
IOS(5, Utils.getAppVersionName());
|
||||
|
||||
final int id;
|
||||
final String version;
|
||||
|
||||
ClientType(int id, String version) {
|
||||
this.id = id;
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -85,7 +85,7 @@ public class SpoofSignaturePatch {
|
|||
*
|
||||
* @param parameters Original protobuf parameter value.
|
||||
*/
|
||||
public static String spoofParameter(String parameters, boolean isShortAndOpeningOrPlaying) {
|
||||
public static String spoofParameter(String parameters, String videoId, boolean isShortAndOpeningOrPlaying) {
|
||||
try {
|
||||
Logger.printDebug(() -> "Original protobuf parameter value: " + parameters);
|
||||
|
||||
|
@ -152,12 +152,12 @@ public class SpoofSignaturePatch {
|
|||
if (Settings.SPOOF_SIGNATURE.get() && !useOriginalStoryboardRenderer) {
|
||||
StoryboardRenderer renderer = getRenderer(false);
|
||||
if (renderer != null) {
|
||||
if (returnNullIfLiveStream && renderer.isLiveStream()) {
|
||||
if (returnNullIfLiveStream && renderer.isLiveStream) {
|
||||
return null;
|
||||
}
|
||||
String spec = renderer.getSpec();
|
||||
if (spec != null) {
|
||||
return spec;
|
||||
|
||||
if (renderer.spec != null) {
|
||||
return renderer.spec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,8 +191,9 @@ public class SpoofSignaturePatch {
|
|||
if (Settings.SPOOF_SIGNATURE.get() && !useOriginalStoryboardRenderer) {
|
||||
StoryboardRenderer renderer = getRenderer(false);
|
||||
if (renderer != null) {
|
||||
Integer recommendedLevel = renderer.getRecommendedLevel();
|
||||
if (recommendedLevel != null) return recommendedLevel;
|
||||
if (renderer.recommendedLevel != null) {
|
||||
return renderer.recommendedLevel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,7 +215,7 @@ public class SpoofSignaturePatch {
|
|||
// Show empty thumbnails so the seek time and chapters still show up.
|
||||
return true;
|
||||
}
|
||||
return renderer.getSpec() != null;
|
||||
return renderer.spec != null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,42 +4,30 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Deprecated
|
||||
public final class StoryboardRenderer {
|
||||
public final String videoId;
|
||||
@Nullable
|
||||
private final String spec;
|
||||
private final boolean isLiveStream;
|
||||
public final String spec;
|
||||
public final boolean isLiveStream;
|
||||
/**
|
||||
* Recommended image quality level, or NULL if no recommendation exists.
|
||||
*/
|
||||
@Nullable
|
||||
private final Integer recommendedLevel;
|
||||
public final Integer recommendedLevel;
|
||||
|
||||
public StoryboardRenderer(@Nullable String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) {
|
||||
public StoryboardRenderer(String videoId, @Nullable String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) {
|
||||
this.videoId = videoId;
|
||||
this.spec = spec;
|
||||
this.isLiveStream = isLiveStream;
|
||||
this.recommendedLevel = recommendedLevel;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getSpec() {
|
||||
return spec;
|
||||
}
|
||||
|
||||
public boolean isLiveStream() {
|
||||
return isLiveStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Recommended image quality level, or NULL if no recommendation exists.
|
||||
*/
|
||||
@Nullable
|
||||
public Integer getRecommendedLevel() {
|
||||
return recommendedLevel;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StoryboardRenderer{" +
|
||||
"isLiveStream=" + isLiveStream +
|
||||
"videoId=" + videoId +
|
||||
", isLiveStream=" + isLiveStream +
|
||||
", spec='" + spec + '\'' +
|
||||
", recommendedLevel=" + recommendedLevel +
|
||||
'}';
|
||||
|
|
|
@ -10,7 +10,6 @@ import org.json.JSONObject;
|
|||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
@Deprecated
|
||||
final class PlayerRoutes {
|
||||
private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/";
|
||||
static final Route.CompiledRoute GET_STORYBOARD_SPEC_RENDERER = new Route(
|
||||
|
@ -27,7 +26,7 @@ final class PlayerRoutes {
|
|||
/**
|
||||
* TCP connection and HTTP read timeout
|
||||
*/
|
||||
private static final int CONNECTION_TIMEOUT_MILLISECONDS = 4 * 1000; // 4 Seconds.
|
||||
private static final int CONNECTION_TIMEOUT_MILLISECONDS = 10 * 1000; // 10 Seconds.
|
||||
|
||||
static {
|
||||
JSONObject innerTubeBody = new JSONObject();
|
||||
|
@ -37,7 +36,7 @@ final class PlayerRoutes {
|
|||
|
||||
JSONObject client = new JSONObject();
|
||||
client.put("clientName", "ANDROID");
|
||||
client.put("clientVersion", Utils.getVersionName());
|
||||
client.put("clientVersion", Utils.getAppVersionName());
|
||||
client.put("androidSdkVersion", 34);
|
||||
|
||||
context.put("client", client);
|
||||
|
@ -85,7 +84,7 @@ final class PlayerRoutes {
|
|||
|
||||
connection.setRequestProperty(
|
||||
"User-Agent", "com.google.android.youtube/" +
|
||||
Utils.getVersionName() +
|
||||
Utils.getAppVersionName() +
|
||||
" (Linux; U; Android 12; GB) gzip"
|
||||
);
|
||||
connection.setRequestProperty("X-Goog-Api-Format-Version", "2");
|
||||
|
|
|
@ -19,17 +19,8 @@ import java.util.Objects;
|
|||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import static app.revanced.integrations.youtube.patches.spoof.requests.PlayerRoutes.*;
|
||||
|
||||
@Deprecated
|
||||
public class StoryboardRendererRequester {
|
||||
|
||||
/**
|
||||
* For videos that have no storyboard.
|
||||
* Usually for low resolution videos as old as YouTube itself.
|
||||
* Does not include paid videos where the renderer fetch fails.
|
||||
*/
|
||||
private static final StoryboardRenderer emptyStoryboard
|
||||
= new StoryboardRenderer(null, false, null);
|
||||
|
||||
private StoryboardRendererRequester() {
|
||||
}
|
||||
|
||||
|
@ -69,9 +60,9 @@ public class StoryboardRendererRequester {
|
|||
null, showToastOnIOException || BaseSettings.DEBUG_TOAST_ON_ERROR.get());
|
||||
connection.disconnect();
|
||||
} catch (SocketTimeoutException ex) {
|
||||
handleConnectionError(str("revanced_spoof_storyboard_timeout"), ex, showToastOnIOException);
|
||||
handleConnectionError(str("revanced_spoof_client_storyboard_timeout"), ex, showToastOnIOException);
|
||||
} catch (IOException ex) {
|
||||
handleConnectionError(str("revanced_spoof_storyboard_io_exception", ex.getMessage()),
|
||||
handleConnectionError(str("revanced_spoof_client_storyboard_io_exception", ex.getMessage()),
|
||||
ex, showToastOnIOException);
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Spoof storyboard fetch failed", ex); // Should never happen.
|
||||
|
@ -98,22 +89,23 @@ public class StoryboardRendererRequester {
|
|||
* @return StoryboardRenderer or null if playabilityStatus is not OK.
|
||||
*/
|
||||
@Nullable
|
||||
private static StoryboardRenderer getStoryboardRendererUsingBody(@NonNull String innerTubeBody,
|
||||
private static StoryboardRenderer getStoryboardRendererUsingBody(String videoId,
|
||||
@NonNull String innerTubeBody,
|
||||
boolean showToastOnIOException) {
|
||||
final JSONObject playerResponse = fetchPlayerResponse(innerTubeBody, showToastOnIOException);
|
||||
if (playerResponse != null && isPlayabilityStatusOk(playerResponse))
|
||||
return getStoryboardRendererUsingResponse(playerResponse);
|
||||
return getStoryboardRendererUsingResponse(videoId, playerResponse);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull JSONObject playerResponse) {
|
||||
private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull String videoId, @NonNull JSONObject playerResponse) {
|
||||
try {
|
||||
Logger.printDebug(() -> "Parsing response: " + playerResponse);
|
||||
if (!playerResponse.has("storyboards")) {
|
||||
Logger.printDebug(() -> "Using empty storyboard");
|
||||
return emptyStoryboard;
|
||||
return new StoryboardRenderer(videoId, null, false, null);
|
||||
}
|
||||
final JSONObject storyboards = playerResponse.getJSONObject("storyboards");
|
||||
final boolean isLiveStream = storyboards.has("playerLiveStoryboardSpecRenderer");
|
||||
|
@ -123,6 +115,7 @@ public class StoryboardRendererRequester {
|
|||
|
||||
final var rendererElement = storyboards.getJSONObject(storyboardsRendererTag);
|
||||
StoryboardRenderer renderer = new StoryboardRenderer(
|
||||
videoId,
|
||||
rendererElement.getString("spec"),
|
||||
isLiveStream,
|
||||
rendererElement.has("recommendedLevel")
|
||||
|
@ -144,11 +137,11 @@ public class StoryboardRendererRequester {
|
|||
public static StoryboardRenderer getStoryboardRenderer(@NonNull String videoId) {
|
||||
Objects.requireNonNull(videoId);
|
||||
|
||||
var renderer = getStoryboardRendererUsingBody(
|
||||
var renderer = getStoryboardRendererUsingBody(videoId,
|
||||
String.format(ANDROID_INNER_TUBE_BODY, videoId), false);
|
||||
if (renderer == null) {
|
||||
Logger.printDebug(() -> videoId + " not available using Android client");
|
||||
renderer = getStoryboardRendererUsingBody(
|
||||
renderer = getStoryboardRendererUsingBody(videoId,
|
||||
String.format(TV_EMBED_INNER_TUBE_BODY, videoId, videoId), true);
|
||||
if (renderer == null) {
|
||||
Logger.printDebug(() -> videoId + " not available using TV embedded client");
|
||||
|
|
|
@ -24,7 +24,10 @@ public class Requester {
|
|||
String url = apiUrl + route.getCompiledRoute();
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setRequestMethod(route.getMethod().name());
|
||||
connection.setRequestProperty("User-Agent", System.getProperty("http.agent") + "; ReVanced/" + Utils.getVersionName());
|
||||
String agentString = System.getProperty("http.agent")
|
||||
+ "; ReVanced/" + Utils.getAppVersionName()
|
||||
+ " (" + Utils.getPatchesReleaseVersion() + ")";
|
||||
connection.setRequestProperty("User-Agent", agentString);
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
@ -32,72 +35,79 @@ public class Requester {
|
|||
/**
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
*/
|
||||
public static String parseJson(HttpURLConnection connection) throws IOException {
|
||||
return parseInputStreamAndClose(connection.getInputStream(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||
*
|
||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
||||
*
|
||||
* @see #parseJson(HttpURLConnection)
|
||||
*/
|
||||
public static String parseJsonAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||
String result = parseJson(connection);
|
||||
connection.disconnect();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
*
|
||||
* @param stripNewLineCharacters if newline (\n) characters should be stripped from the InputStream
|
||||
*/
|
||||
public static String parseInputStreamAndClose(InputStream inputStream, boolean stripNewLineCharacters) throws IOException {
|
||||
private static String parseInputStreamAndClose(InputStream inputStream) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||
StringBuilder jsonBuilder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
jsonBuilder.append(line);
|
||||
if (!stripNewLineCharacters)
|
||||
jsonBuilder.append("\n");
|
||||
jsonBuilder.append("\n");
|
||||
}
|
||||
return jsonBuilder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
* Parse the {@link HttpURLConnection} response as a String.
|
||||
* This does not close the url connection. If further requests to this host are unlikely
|
||||
* in the near future, then instead use {@link #parseStringAndDisconnect(HttpURLConnection)}.
|
||||
*/
|
||||
public static String parseErrorJson(HttpURLConnection connection) throws IOException {
|
||||
return parseInputStreamAndClose(connection.getErrorStream(), false);
|
||||
public static String parseString(HttpURLConnection connection) throws IOException {
|
||||
return parseInputStreamAndClose(connection.getInputStream());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||
* Parse the {@link HttpURLConnection} response as a String, and disconnect.
|
||||
*
|
||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
||||
* <b>Should only be used if other requests to the server in the near future are unlikely</b>
|
||||
*
|
||||
* @see #parseErrorJson(HttpURLConnection)
|
||||
* @see #parseString(HttpURLConnection)
|
||||
*/
|
||||
public static String parseErrorJsonAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||
String result = parseErrorJson(connection);
|
||||
public static String parseStringAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||
String result = parseString(connection);
|
||||
connection.disconnect();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
* Parse the {@link HttpURLConnection} error stream as a String.
|
||||
* If the server sent no error response data, this returns an empty string.
|
||||
*/
|
||||
public static String parseErrorString(HttpURLConnection connection) throws IOException {
|
||||
InputStream errorStream = connection.getErrorStream();
|
||||
if (errorStream == null) {
|
||||
return "";
|
||||
}
|
||||
return parseInputStreamAndClose(errorStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection} error stream as a String, and disconnect.
|
||||
* If the server sent no error response data, this returns an empty string.
|
||||
*
|
||||
* Should only be used if other requests to the server are unlikely in the near future.
|
||||
*
|
||||
* @see #parseErrorString(HttpURLConnection)
|
||||
*/
|
||||
public static String parseErrorStringAndDisconnect(HttpURLConnection connection) throws IOException {
|
||||
String result = parseErrorString(connection);
|
||||
connection.disconnect();
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection} response into a JSONObject.
|
||||
* This does not close the url connection. If further requests to this host are unlikely
|
||||
* in the near future, then instead use {@link #parseJSONObjectAndDisconnect(HttpURLConnection)}.
|
||||
*/
|
||||
public static JSONObject parseJSONObject(HttpURLConnection connection) throws JSONException, IOException {
|
||||
return new JSONObject(parseJson(connection));
|
||||
return new JSONObject(parseString(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||
*
|
||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
||||
* <b>Should only be used if other requests to the server in the near future are unlikely</b>
|
||||
*
|
||||
* @see #parseJSONObject(HttpURLConnection)
|
||||
*/
|
||||
|
@ -109,15 +119,17 @@ public class Requester {
|
|||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, and closes the underlying InputStream.
|
||||
* This does not close the url connection. If further requests to this host are unlikely
|
||||
* in the near future, then instead use {@link #parseJSONArrayAndDisconnect(HttpURLConnection)}.
|
||||
*/
|
||||
public static JSONArray parseJSONArray(HttpURLConnection connection) throws JSONException, IOException {
|
||||
return new JSONArray(parseJson(connection));
|
||||
return new JSONArray(parseString(connection));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the {@link HttpURLConnection}, close the underlying InputStream, and disconnect.
|
||||
*
|
||||
* <b>Should only be used if other requests to the server are unlikely in the near future</b>
|
||||
* <b>Should only be used if other requests to the server in the near future are unlikely</b>
|
||||
*
|
||||
* @see #parseJSONArray(HttpURLConnection)
|
||||
*/
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package app.revanced.integrations.youtube.returnyoutubedislike.requests;
|
||||
|
||||
import static app.revanced.integrations.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute;
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import static app.revanced.integrations.youtube.returnyoutubedislike.requests.ReturnYouTubeDislikeRoutes.getRYDConnectionFromRoute;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
|
@ -22,11 +22,11 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.SecureRandom;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.requests.Requester;
|
||||
import app.revanced.integrations.youtube.returnyoutubedislike.ReturnYouTubeDislike;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
|
||||
public class ReturnYouTubeDislikeApi {
|
||||
/**
|
||||
|
@ -279,7 +279,7 @@ public class ReturnYouTubeDislikeApi {
|
|||
}
|
||||
|
||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||
// do not disconnect, the same server connection will likely be used again soon
|
||||
// Do not disconnect, the same server connection will likely be used again soon.
|
||||
JSONObject json = Requester.parseJSONObject(connection);
|
||||
try {
|
||||
RYDVoteData votingData = new RYDVoteData(json);
|
||||
|
@ -377,20 +377,17 @@ public class ReturnYouTubeDislikeApi {
|
|||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||
return null;
|
||||
}
|
||||
String result = null;
|
||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||
result = Requester.parseJson(connection);
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
Logger.printDebug(() -> "Registration confirmation successful");
|
||||
return userId;
|
||||
}
|
||||
Logger.printDebug(() -> "Registration confirmation successful");
|
||||
return userId;
|
||||
}
|
||||
final String resultLog = result == null ? "(no response)" : result;
|
||||
|
||||
// Something went wrong, might as well disconnect.
|
||||
String response = Requester.parseStringAndDisconnect(connection);
|
||||
Logger.printInfo(() -> "Failed to confirm registration for user: " + userId
|
||||
+ " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog);
|
||||
+ " solution: " + solution + " responseCode: " + responseCode + " response: '" + response + "''");
|
||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
||||
null, true);
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
} catch (SocketTimeoutException ex) {
|
||||
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
|
||||
} catch (IOException ex) {
|
||||
|
@ -461,6 +458,7 @@ public class ReturnYouTubeDislikeApi {
|
|||
String solution = solvePuzzle(challenge, difficulty);
|
||||
return confirmVote(videoId, userId, solution);
|
||||
}
|
||||
|
||||
Logger.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote
|
||||
+ " response code was: " + responseCode);
|
||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
||||
|
@ -501,20 +499,17 @@ public class ReturnYouTubeDislikeApi {
|
|||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||
return false;
|
||||
}
|
||||
String result = null;
|
||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||
result = Requester.parseJson(connection);
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
Logger.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
||||
return true;
|
||||
}
|
||||
Logger.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
||||
return true;
|
||||
}
|
||||
final String resultLog = result == null ? "(no response)" : result;
|
||||
|
||||
// Something went wrong, might as well disconnect.
|
||||
String response = Requester.parseStringAndDisconnect(connection);
|
||||
Logger.printInfo(() -> "Failed to confirm vote for video: " + videoId
|
||||
+ " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog);
|
||||
+ " solution: " + solution + " responseCode: " + responseCode + " response: '" + response + "'");
|
||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode),
|
||||
null, true);
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
} catch (SocketTimeoutException ex) {
|
||||
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex, false);
|
||||
} catch (IOException ex) {
|
||||
|
|
|
@ -3,6 +3,10 @@ package app.revanced.integrations.youtube.settings;
|
|||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.settings.*;
|
||||
import app.revanced.integrations.shared.settings.preference.SharedPrefCategory;
|
||||
import app.revanced.integrations.youtube.patches.AlternativeThumbnailsPatch.DeArrowAvailability;
|
||||
import app.revanced.integrations.youtube.patches.AlternativeThumbnailsPatch.StillImagesAvailability;
|
||||
import app.revanced.integrations.youtube.patches.AlternativeThumbnailsPatch.ThumbnailOption;
|
||||
import app.revanced.integrations.youtube.patches.AlternativeThumbnailsPatch.ThumbnailStillTime;
|
||||
import app.revanced.integrations.youtube.patches.spoof.SpoofAppVersionPatch;
|
||||
import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings;
|
||||
|
||||
|
@ -16,18 +20,9 @@ import static java.lang.Boolean.FALSE;
|
|||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
public class Settings extends BaseSettings {
|
||||
// External downloader
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||
|
||||
// Copy video URL
|
||||
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);
|
||||
|
||||
// Video
|
||||
public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE);
|
||||
public static final BooleanSetting HIDE_VIDEO_QUALITY_MENU_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", TRUE);
|
||||
public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", TRUE);
|
||||
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);
|
||||
|
@ -45,42 +40,50 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_GET_PREMIUM = new BooleanSetting("revanced_hide_get_premium", TRUE);
|
||||
public static final BooleanSetting HIDE_HIDE_LATEST_POSTS = new BooleanSetting("revanced_hide_latest_posts_ads", TRUE);
|
||||
public static final BooleanSetting HIDE_MERCHANDISE_BANNERS = new BooleanSetting("revanced_hide_merchandise_banners", TRUE);
|
||||
public static final BooleanSetting HIDE_PAID_CONTENT = new BooleanSetting("revanced_hide_paid_content_ads", TRUE);
|
||||
public static final BooleanSetting HIDE_PAID_PROMOTION_LABEL = new BooleanSetting("revanced_hide_paid_promotion_label", TRUE);
|
||||
public static final BooleanSetting HIDE_PRODUCTS_BANNER = new BooleanSetting("revanced_hide_products_banner", TRUE);
|
||||
public static final BooleanSetting HIDE_SHOPPING_LINKS = new BooleanSetting("revanced_hide_shopping_links", TRUE);
|
||||
public static final BooleanSetting HIDE_SELF_SPONSOR = new BooleanSetting("revanced_hide_self_sponsor_ads", TRUE);
|
||||
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_hide_video_ads", TRUE, true);
|
||||
public static final BooleanSetting HIDE_VISIT_STORE_BUTTON = new BooleanSetting("revanced_hide_visit_store_button", TRUE);
|
||||
public static final BooleanSetting HIDE_WEB_SEARCH_RESULTS = new BooleanSetting("revanced_hide_web_search_results", TRUE);
|
||||
// Layout
|
||||
public static final BooleanSetting ALT_THUMBNAIL_STILLS = new BooleanSetting("revanced_alt_thumbnail_stills", FALSE);
|
||||
public static final IntegerSetting ALT_THUMBNAIL_STILLS_TIME = new IntegerSetting("revanced_alt_thumbnail_stills_time", 2, parent(ALT_THUMBNAIL_STILLS));
|
||||
public static final BooleanSetting ALT_THUMBNAIL_STILLS_FAST = new BooleanSetting("revanced_alt_thumbnail_stills_fast", FALSE, parent(ALT_THUMBNAIL_STILLS));
|
||||
public static final BooleanSetting ALT_THUMBNAIL_DEARROW = new BooleanSetting("revanced_alt_thumbnail_dearrow", FALSE);
|
||||
public static final StringSetting ALT_THUMBNAIL_DEARROW_API_URL = new StringSetting("revanced_alt_thumbnail_dearrow_api_url",
|
||||
"https://dearrow-thumb.ajay.app/api/v1/getThumbnail", true, parent(ALT_THUMBNAIL_DEARROW));
|
||||
public static final BooleanSetting ALT_THUMBNAIL_DEARROW_CONNECTION_TOAST = new BooleanSetting("revanced_alt_thumbnail_dearrow_connection_toast", TRUE, parent(ALT_THUMBNAIL_DEARROW));
|
||||
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
||||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||
public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", 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);
|
||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE);
|
||||
|
||||
// Feed
|
||||
public static final BooleanSetting HIDE_ALBUM_CARDS = new BooleanSetting("revanced_hide_album_cards", FALSE, true);
|
||||
public static final BooleanSetting HIDE_ARTIST_CARDS = new BooleanSetting("revanced_hide_artist_cards", FALSE);
|
||||
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_BREAKING_NEWS = new BooleanSetting("revanced_hide_breaking_news", TRUE, true);
|
||||
|
||||
// Alternative thumbnails
|
||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_HOME = new EnumSetting<>("revanced_alt_thumbnail_home", ThumbnailOption.ORIGINAL);
|
||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_SUBSCRIPTIONS = new EnumSetting<>("revanced_alt_thumbnail_subscription", ThumbnailOption.ORIGINAL);
|
||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_LIBRARY = new EnumSetting<>("revanced_alt_thumbnail_library", ThumbnailOption.ORIGINAL);
|
||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_PLAYER = new EnumSetting<>("revanced_alt_thumbnail_player", ThumbnailOption.ORIGINAL);
|
||||
public static final EnumSetting<ThumbnailOption> ALT_THUMBNAIL_SEARCH = new EnumSetting<>("revanced_alt_thumbnail_search", ThumbnailOption.ORIGINAL);
|
||||
public static final StringSetting ALT_THUMBNAIL_DEARROW_API_URL = new StringSetting("revanced_alt_thumbnail_dearrow_api_url",
|
||||
"https://dearrow-thumb.ajay.app/api/v1/getThumbnail", true, new DeArrowAvailability());
|
||||
public static final BooleanSetting ALT_THUMBNAIL_DEARROW_CONNECTION_TOAST = new BooleanSetting("revanced_alt_thumbnail_dearrow_connection_toast", TRUE, new DeArrowAvailability());
|
||||
public static final EnumSetting<ThumbnailStillTime> ALT_THUMBNAIL_STILLS_TIME = new EnumSetting<>("revanced_alt_thumbnail_stills_time", ThumbnailStillTime.MIDDLE, new StillImagesAvailability());
|
||||
public static final BooleanSetting ALT_THUMBNAIL_STILLS_FAST = new BooleanSetting("revanced_alt_thumbnail_stills_fast", FALSE, new StillImagesAvailability());
|
||||
|
||||
// Hide keyword content
|
||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_HOME = new BooleanSetting("revanced_hide_keyword_content_home", FALSE);
|
||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_keyword_content_subscriptions", FALSE);
|
||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SEARCH = new BooleanSetting("revanced_hide_keyword_content_search", FALSE);
|
||||
public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
|
||||
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
||||
|
||||
// Uncategorized layout related settings. Do not add to this section, and instead move these out and categorize them.
|
||||
public static final BooleanSetting DISABLE_SUGGESTED_VIDEO_END_SCREEN = new BooleanSetting("revanced_disable_suggested_video_end_screen", FALSE, true);
|
||||
public static final BooleanSetting GRADIENT_LOADING_SCREEN = new BooleanSetting("revanced_gradient_loading_screen", FALSE);
|
||||
public static final BooleanSetting HIDE_HORIZONTAL_SHELVES = new BooleanSetting("revanced_hide_horizontal_shelves", TRUE);
|
||||
public static final BooleanSetting HIDE_CAPTIONS_BUTTON = new BooleanSetting("revanced_hide_captions_button", FALSE);
|
||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_CHANNEL_BAR = new BooleanSetting("revanced_hide_channel_bar", FALSE);
|
||||
public static final BooleanSetting HIDE_CHANNEL_MEMBER_SHELF = new BooleanSetting("revanced_hide_channel_member_shelf", TRUE);
|
||||
public static final BooleanSetting HIDE_CHIPS_SHELF = new BooleanSetting("revanced_hide_chips_shelf", TRUE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE, true);
|
||||
public static final BooleanSetting HIDE_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_community_guidelines", TRUE);
|
||||
public static final BooleanSetting HIDE_COMMUNITY_POSTS = new BooleanSetting("revanced_hide_community_posts", FALSE);
|
||||
public static final BooleanSetting HIDE_COMPACT_BANNER = new BooleanSetting("revanced_hide_compact_banner", TRUE);
|
||||
public static final BooleanSetting HIDE_CROWDFUNDING_BOX = new BooleanSetting("revanced_hide_crowdfunding_box", FALSE, true);
|
||||
public static final BooleanSetting HIDE_EMAIL_ADDRESS = new BooleanSetting("revanced_hide_email_address", FALSE);
|
||||
@Deprecated public static final BooleanSetting HIDE_EMAIL_ADDRESS = new BooleanSetting("revanced_hide_email_address", FALSE);
|
||||
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", TRUE);
|
||||
public static final BooleanSetting HIDE_EXPANDABLE_CHIP = new BooleanSetting("revanced_hide_expandable_chip", TRUE);
|
||||
|
@ -96,18 +99,13 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_IMAGE_SHELF = new BooleanSetting("revanced_hide_image_shelf", TRUE);
|
||||
public static final BooleanSetting HIDE_INFO_CARDS = new BooleanSetting("revanced_hide_info_cards", TRUE);
|
||||
public static final BooleanSetting HIDE_JOIN_MEMBERSHIP_BUTTON = new BooleanSetting("revanced_hide_join_membership_button", TRUE);
|
||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_HOME = new BooleanSetting("revanced_hide_keyword_content_home", FALSE);
|
||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_keyword_content_subscriptions", FALSE);
|
||||
public static final BooleanSetting HIDE_KEYWORD_CONTENT_SEARCH = new BooleanSetting("revanced_hide_keyword_content_search", FALSE);
|
||||
public static final StringSetting HIDE_KEYWORD_CONTENT_PHRASES = new StringSetting("revanced_hide_keyword_content_phrases", "",
|
||||
parentsAny(HIDE_KEYWORD_CONTENT_HOME, HIDE_KEYWORD_CONTENT_SUBSCRIPTIONS, HIDE_KEYWORD_CONTENT_SEARCH));
|
||||
public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE, true);
|
||||
@Deprecated public static final BooleanSetting HIDE_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE);
|
||||
public static final BooleanSetting HIDE_SHOW_MORE_BUTTON = new BooleanSetting("revanced_hide_show_more_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_MEDICAL_PANELS = new BooleanSetting("revanced_hide_medical_panels", TRUE);
|
||||
public static final BooleanSetting HIDE_MIX_PLAYLISTS = new BooleanSetting("revanced_hide_mix_playlists", TRUE);
|
||||
public static final BooleanSetting HIDE_MOVIES_SECTION = new BooleanSetting("revanced_hide_movies_section", TRUE);
|
||||
public static final BooleanSetting HIDE_NOTIFY_ME_BUTTON = new BooleanSetting("revanced_hide_notify_me_button", TRUE);
|
||||
public static final BooleanSetting HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE);
|
||||
public static final BooleanSetting HIDE_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_preview_comment", FALSE, true);
|
||||
public static final BooleanSetting HIDE_PLAYABLES = new BooleanSetting("revanced_hide_playables", TRUE);
|
||||
public static final BooleanSetting HIDE_QUICK_ACTIONS = new BooleanSetting("revanced_hide_quick_actions", FALSE);
|
||||
public static final BooleanSetting HIDE_RELATED_VIDEOS = new BooleanSetting("revanced_hide_related_videos", FALSE);
|
||||
public static final BooleanSetting HIDE_SEARCH_RESULT_SHELF_HEADER = new BooleanSetting("revanced_hide_search_result_shelf_header", FALSE);
|
||||
|
@ -116,23 +114,29 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_TIMESTAMP = new BooleanSetting("revanced_hide_timestamp", FALSE);
|
||||
public static final BooleanSetting HIDE_VIDEO_CHANNEL_WATERMARK = new BooleanSetting("revanced_hide_channel_watermark", TRUE);
|
||||
public static final BooleanSetting HIDE_FOR_YOU_SHELF = new BooleanSetting("revanced_hide_for_you_shelf", TRUE);
|
||||
public static final BooleanSetting HIDE_VIDEO_QUALITY_MENU_FOOTER = new BooleanSetting("revanced_hide_video_quality_menu_footer", TRUE);
|
||||
public static final BooleanSetting HIDE_SEARCH_RESULT_RECOMMENDATIONS = new BooleanSetting("revanced_hide_search_result_recommendations", TRUE);
|
||||
public static final IntegerSetting PLAYER_OVERLAY_OPACITY = new IntegerSetting("revanced_player_overlay_opacity",100, true);
|
||||
public static final BooleanSetting PLAYER_POPUP_PANELS = new BooleanSetting("revanced_hide_player_popup_panels", FALSE);
|
||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "17.33.42", true, parent(SPOOF_APP_VERSION));
|
||||
public static final BooleanSetting TABLET_LAYOUT = new BooleanSetting("revanced_tablet_layout", FALSE, true, "revanced_tablet_layout_user_dialog_message");
|
||||
public static final BooleanSetting USE_TABLET_MINIPLAYER = new BooleanSetting("revanced_tablet_miniplayer", FALSE, true);
|
||||
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
||||
public static final StringSetting START_PAGE = new StringSetting("revanced_start_page", "");
|
||||
|
||||
// Navigation buttons
|
||||
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true);
|
||||
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 SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true);
|
||||
// Player
|
||||
public static final BooleanSetting DISABLE_FULLSCREEN_AMBIENT_MODE = new BooleanSetting("revanced_disable_fullscreen_ambient_mode", TRUE, true);
|
||||
public static final BooleanSetting DISABLE_ROLLING_NUMBER_ANIMATIONS = new BooleanSetting("revanced_disable_rolling_number_animations", FALSE);
|
||||
public static final BooleanSetting HIDE_AUTOPLAY_BUTTON = new BooleanSetting("revanced_hide_autoplay_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_CAST_BUTTON = new BooleanSetting("revanced_hide_cast_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_PLAYER_BUTTONS = new BooleanSetting("revanced_hide_player_buttons", FALSE);
|
||||
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);
|
||||
|
||||
// External downloader
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_external_downloader", FALSE);
|
||||
public static final BooleanSetting EXTERNAL_DOWNLOADER_ACTION_BUTTON = new BooleanSetting("revanced_external_downloader_action_button", FALSE);
|
||||
public static final StringSetting EXTERNAL_DOWNLOADER_PACKAGE_NAME = new StringSetting("revanced_external_downloader_name",
|
||||
"org.schabi.newpipe" /* NewPipe */, parentsAny(EXTERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER_ACTION_BUTTON));
|
||||
|
||||
// Comments
|
||||
public static final BooleanSetting HIDE_PREVIEW_COMMENT = new BooleanSetting("revanced_hide_preview_comment", FALSE);
|
||||
public static final BooleanSetting HIDE_COMMENTS_SECTION = new BooleanSetting("revanced_hide_comments_section", FALSE);
|
||||
public static final BooleanSetting HIDE_COMMENT_TIMESTAMP_AND_EMOJI_BUTTONS = new BooleanSetting("revanced_hide_comment_timestamp_and_emoji_buttons", TRUE);
|
||||
|
||||
// Description
|
||||
public static final BooleanSetting HIDE_CHAPTERS = new BooleanSetting("revanced_hide_chapters", TRUE);
|
||||
|
@ -140,17 +144,66 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_GAME_SECTION = new BooleanSetting("revanced_hide_game_section", TRUE);
|
||||
public static final BooleanSetting HIDE_MUSIC_SECTION = new BooleanSetting("revanced_hide_music_section", TRUE);
|
||||
public static final BooleanSetting HIDE_PODCAST_SECTION = new BooleanSetting("revanced_hide_podcast_section", TRUE);
|
||||
public static final BooleanSetting HIDE_TRANSCIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||
public static final BooleanSetting HIDE_TRANSCRIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||
|
||||
// Action buttons
|
||||
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE);
|
||||
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
|
||||
public static final BooleanSetting HIDE_REMIX_BUTTON = new BooleanSetting("revanced_hide_remix_button", TRUE);
|
||||
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
||||
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", TRUE);
|
||||
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
|
||||
public static final BooleanSetting HIDE_PLAYLIST_BUTTON = new BooleanSetting("revanced_hide_playlist_button", FALSE);
|
||||
|
||||
// Player flyout menu items
|
||||
public static final BooleanSetting HIDE_CAPTIONS_MENU = new BooleanSetting("revanced_hide_player_flyout_captions", FALSE);
|
||||
public static final BooleanSetting HIDE_ADDITIONAL_SETTINGS_MENU = new BooleanSetting("revanced_hide_player_flyout_additional_settings", FALSE);
|
||||
public static final BooleanSetting HIDE_LOOP_VIDEO_MENU = new BooleanSetting("revanced_hide_player_flyout_loop_video", FALSE);
|
||||
public static final BooleanSetting HIDE_AMBIENT_MODE_MENU = new BooleanSetting("revanced_hide_player_flyout_ambient_mode", FALSE);
|
||||
public static final BooleanSetting HIDE_HELP_MENU = new BooleanSetting("revanced_hide_player_flyout_help", TRUE);
|
||||
public static final BooleanSetting HIDE_SPEED_MENU = new BooleanSetting("revanced_hide_player_flyout_speed", FALSE);
|
||||
public static final BooleanSetting HIDE_MORE_INFO_MENU = new BooleanSetting("revanced_hide_player_flyout_more_info", TRUE);
|
||||
public static final BooleanSetting HIDE_LOCK_SCREEN_MENU = new BooleanSetting("revanced_hide_player_flyout_lock_screen", FALSE);
|
||||
public static final BooleanSetting HIDE_AUDIO_TRACK_MENU = new BooleanSetting("revanced_hide_player_flyout_audio_track", FALSE);
|
||||
public static final BooleanSetting HIDE_WATCH_IN_VR_MENU = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
|
||||
|
||||
// General layout
|
||||
public static final StringSetting START_PAGE = new StringSetting("revanced_start_page", "");
|
||||
public static final BooleanSetting SPOOF_APP_VERSION = new BooleanSetting("revanced_spoof_app_version", FALSE, true, "revanced_spoof_app_version_user_dialog_message");
|
||||
public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "17.33.42", true, parent(SPOOF_APP_VERSION));
|
||||
public static final BooleanSetting TABLET_LAYOUT = new BooleanSetting("revanced_tablet_layout", FALSE, true, "revanced_tablet_layout_user_dialog_message");
|
||||
public static final BooleanSetting USE_TABLET_MINIPLAYER = new BooleanSetting("revanced_tablet_miniplayer", FALSE, true);
|
||||
public static final BooleanSetting WIDE_SEARCHBAR = new BooleanSetting("revanced_wide_searchbar", FALSE, true);
|
||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG = new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
||||
|
||||
// Custom filter
|
||||
public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE);
|
||||
public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER));
|
||||
|
||||
// Navigation buttons
|
||||
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true);
|
||||
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);
|
||||
|
||||
// Shorts
|
||||
@Deprecated public static final BooleanSetting DEPRECATED_HIDE_SHORTS = new BooleanSetting("revanced_hide_shorts", FALSE);
|
||||
public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_HOME = new BooleanSetting("revanced_hide_shorts_home", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_shorts_subscriptions", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SEARCH = new BooleanSetting("revanced_hide_shorts_search", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_JOIN_BUTTON = new BooleanSetting("revanced_hide_shorts_join_button", TRUE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON = new BooleanSetting("revanced_hide_shorts_subscribe_button", TRUE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED = new BooleanSetting("revanced_hide_shorts_subscribe_button_paused", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_thanks_button", TRUE);
|
||||
public static final BooleanSetting HIDE_SHORTS_PAUSED_OVERLAY_BUTTONS = new BooleanSetting("revanced_hide_shorts_paused_overlay_buttons", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SHOP_BUTTON = new BooleanSetting("revanced_hide_shorts_shop_button", TRUE);
|
||||
public static final BooleanSetting HIDE_SHORTS_TAGGED_PRODUCTS = new BooleanSetting("revanced_hide_shorts_tagged_products", TRUE);
|
||||
public static final BooleanSetting HIDE_SHORTS_LOCATION_LABEL = new BooleanSetting("revanced_hide_shorts_location_label", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SAVE_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_save_sound_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SEARCH_SUGGESTIONS = new BooleanSetting("revanced_hide_shorts_search_suggestions", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SUPER_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_super_thanks_button", TRUE);
|
||||
public static final BooleanSetting HIDE_SHORTS_LIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_like_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_shorts_dislike_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_COMMENTS_BUTTON = new BooleanSetting("revanced_hide_shorts_comments_button", FALSE);
|
||||
|
@ -165,58 +218,45 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_SHORTS_NAVIGATION_BAR = new BooleanSetting("revanced_hide_shorts_navigation_bar", TRUE, true);
|
||||
|
||||
// Seekbar
|
||||
public static final BooleanSetting DISABLE_PRECISE_SEEKING_GESTURE = new BooleanSetting("revanced_disable_precise_seeking_gesture", TRUE);
|
||||
public static final BooleanSetting SEEKBAR_TAPPING = new BooleanSetting("revanced_seekbar_tapping", TRUE);
|
||||
public static final BooleanSetting SLIDE_TO_SEEK = new BooleanSetting("revanced_slide_to_seek", FALSE);
|
||||
public static final BooleanSetting RESTORE_OLD_SEEKBAR_THUMBNAILS = new BooleanSetting("revanced_restore_old_seekbar_thumbnails", TRUE);
|
||||
public static final BooleanSetting HIDE_SEEKBAR = new BooleanSetting("revanced_hide_seekbar", FALSE, true);
|
||||
public static final BooleanSetting HIDE_SEEKBAR_THUMBNAIL = new BooleanSetting("revanced_hide_seekbar_thumbnail", FALSE);
|
||||
public static final BooleanSetting SEEKBAR_CUSTOM_COLOR = new BooleanSetting("revanced_seekbar_custom_color", FALSE, true);
|
||||
public static final StringSetting SEEKBAR_CUSTOM_COLOR_VALUE = new StringSetting("revanced_seekbar_custom_color_value", "#FF0000", true, parent(SEEKBAR_CUSTOM_COLOR));
|
||||
|
||||
// Action buttons
|
||||
public static final BooleanSetting HIDE_LIKE_DISLIKE_BUTTON = new BooleanSetting("revanced_hide_like_dislike_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHARE_BUTTON = new BooleanSetting("revanced_hide_share_button", FALSE);
|
||||
public static final BooleanSetting HIDE_REPORT_BUTTON = new BooleanSetting("revanced_hide_report_button", FALSE);
|
||||
public static final BooleanSetting HIDE_REMIX_BUTTON = new BooleanSetting("revanced_hide_remix_button", TRUE);
|
||||
public static final BooleanSetting HIDE_DOWNLOAD_BUTTON = new BooleanSetting("revanced_hide_download_button", FALSE);
|
||||
public static final BooleanSetting HIDE_THANKS_BUTTON = new BooleanSetting("revanced_hide_thanks_button", TRUE);
|
||||
public static final BooleanSetting HIDE_CLIP_BUTTON = new BooleanSetting("revanced_hide_clip_button", TRUE);
|
||||
public static final BooleanSetting HIDE_PLAYLIST_BUTTON = new BooleanSetting("revanced_hide_playlist_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHOP_BUTTON = new BooleanSetting("revanced_hide_shop_button", TRUE);
|
||||
|
||||
// Player flyout menu items
|
||||
public static final BooleanSetting HIDE_CAPTIONS_MENU = new BooleanSetting("revanced_hide_player_flyout_captions", FALSE);
|
||||
public static final BooleanSetting HIDE_ADDITIONAL_SETTINGS_MENU = new BooleanSetting("revanced_hide_player_flyout_additional_settings", FALSE);
|
||||
public static final BooleanSetting HIDE_LOOP_VIDEO_MENU = new BooleanSetting("revanced_hide_player_flyout_loop_video", FALSE);
|
||||
public static final BooleanSetting HIDE_AMBIENT_MODE_MENU = new BooleanSetting("revanced_hide_player_flyout_ambient_mode", FALSE);
|
||||
public static final BooleanSetting HIDE_REPORT_MENU = new BooleanSetting("revanced_hide_player_flyout_report", TRUE);
|
||||
public static final BooleanSetting HIDE_HELP_MENU = new BooleanSetting("revanced_hide_player_flyout_help", TRUE);
|
||||
public static final BooleanSetting HIDE_SPEED_MENU = new BooleanSetting("revanced_hide_player_flyout_speed", FALSE);
|
||||
public static final BooleanSetting HIDE_MORE_INFO_MENU = new BooleanSetting("revanced_hide_player_flyout_more_info", TRUE);
|
||||
public static final BooleanSetting HIDE_AUDIO_TRACK_MENU = new BooleanSetting("revanced_hide_player_flyout_audio_track", FALSE);
|
||||
public static final BooleanSetting HIDE_WATCH_IN_VR_MENU = new BooleanSetting("revanced_hide_player_flyout_watch_in_vr", TRUE);
|
||||
|
||||
// Misc
|
||||
public static final BooleanSetting AUTO_CAPTIONS = new BooleanSetting("revanced_auto_captions", FALSE);
|
||||
public static final BooleanSetting DISABLE_ZOOM_HAPTICS = new BooleanSetting("revanced_disable_zoom_haptics", TRUE);
|
||||
public static final BooleanSetting EXTERNAL_BROWSER = new BooleanSetting("revanced_external_browser", TRUE, true);
|
||||
public static final BooleanSetting AUTO_REPEAT = new BooleanSetting("revanced_auto_repeat", FALSE);
|
||||
public static final BooleanSetting SEEKBAR_TAPPING = new BooleanSetting("revanced_seekbar_tapping", TRUE);
|
||||
public static final BooleanSetting SLIDE_TO_SEEK = new BooleanSetting("revanced_slide_to_seek", FALSE);
|
||||
public static final BooleanSetting DISABLE_PRECISE_SEEKING_GESTURE = new BooleanSetting("revanced_disable_precise_seeking_gesture", TRUE);
|
||||
public static final BooleanSetting SPOOF_SIGNATURE = new BooleanSetting("revanced_spoof_signature_verification_enabled", TRUE, true,
|
||||
"revanced_spoof_signature_verification_enabled_user_dialog_message");
|
||||
public static final BooleanSetting SPOOF_SIGNATURE_IN_FEED = new BooleanSetting("revanced_spoof_signature_in_feed_enabled", FALSE, false,
|
||||
parent(SPOOF_SIGNATURE));
|
||||
public static final BooleanSetting SPOOF_STORYBOARD_RENDERER = new BooleanSetting("revanced_spoof_storyboard", TRUE, true,
|
||||
parent(SPOOF_SIGNATURE));
|
||||
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true);
|
||||
public static final BooleanSetting SPOOF_DEVICE_DIMENSIONS = new BooleanSetting("revanced_spoof_device_dimensions", FALSE, true,
|
||||
"revanced_spoof_device_dimensions_user_dialog_message");
|
||||
public static final BooleanSetting BYPASS_URL_REDIRECTS = new BooleanSetting("revanced_bypass_url_redirects", TRUE);
|
||||
public static final BooleanSetting ANNOUNCEMENTS = new BooleanSetting("revanced_announcements", TRUE);
|
||||
@Deprecated
|
||||
public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", TRUE, true, "revanced_spoof_client_user_dialog_message");
|
||||
public static final BooleanSetting SPOOF_CLIENT_USE_IOS = new BooleanSetting("revanced_spoof_client_use_ios", FALSE, true, parent(SPOOF_CLIENT));
|
||||
@Deprecated
|
||||
public static final StringSetting DEPRECATED_ANNOUNCEMENT_LAST_HASH = new StringSetting("revanced_announcement_last_hash", "");
|
||||
public static final IntegerSetting ANNOUNCEMENT_LAST_ID = new IntegerSetting("revanced_announcement_last_id", -1);
|
||||
public static final BooleanSetting REMOVE_TRACKING_QUERY_PARAMETER = new BooleanSetting("revanced_remove_tracking_query_parameter", TRUE);
|
||||
public static final BooleanSetting REMOVE_VIEWER_DISCRETION_DIALOG= new BooleanSetting("revanced_remove_viewer_discretion_dialog", FALSE,
|
||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message");
|
||||
|
||||
// Debugging
|
||||
/**
|
||||
* When enabled, share the debug logs with care.
|
||||
* The buffer contains select user data, including the client ip address and information that could identify the end user.
|
||||
*/
|
||||
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
|
||||
|
||||
// Old deprecated signature spoofing
|
||||
@Deprecated public static final BooleanSetting SPOOF_SIGNATURE = new BooleanSetting("revanced_spoof_signature_verification_enabled", TRUE, true, false,
|
||||
"revanced_spoof_signature_verification_enabled_user_dialog_message", null);
|
||||
@Deprecated public static final BooleanSetting SPOOF_SIGNATURE_IN_FEED = new BooleanSetting("revanced_spoof_signature_in_feed_enabled", FALSE, false, false, null,
|
||||
parent(SPOOF_SIGNATURE));
|
||||
@Deprecated public static final BooleanSetting SPOOF_STORYBOARD_RENDERER = new BooleanSetting("revanced_spoof_storyboard", TRUE, true, false, null,
|
||||
parent(SPOOF_SIGNATURE));
|
||||
|
||||
// Swipe controls
|
||||
public static final BooleanSetting SWIPE_BRIGHTNESS = new BooleanSetting("revanced_swipe_brightness", TRUE);
|
||||
|
@ -233,15 +273,9 @@ public class Settings extends BaseSettings {
|
|||
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));
|
||||
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true,
|
||||
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
|
||||
|
||||
// Debugging
|
||||
/**
|
||||
* When enabled, share the debug logs with care.
|
||||
* The buffer contains select user data, including the client ip address and information that could identify the YT account.
|
||||
*/
|
||||
public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, parent(BaseSettings.DEBUG));
|
||||
public static final BooleanSetting SWIPE_SAVE_AND_RESTORE_BRIGHTNESS = new BooleanSetting("revanced_swipe_save_and_restore_brightness", TRUE, true, parent(SWIPE_BRIGHTNESS));
|
||||
public static final FloatSetting SWIPE_BRIGHTNESS_VALUE = new FloatSetting("revanced_swipe_brightness_value", -1f);
|
||||
public static final BooleanSetting SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS = new BooleanSetting("revanced_swipe_lowest_value_enable_auto_brightness", FALSE, true, parent(SWIPE_BRIGHTNESS));
|
||||
|
||||
// ReturnYoutubeDislike
|
||||
public static final BooleanSetting RYD_ENABLED = new BooleanSetting("ryd_enabled", TRUE);
|
||||
|
@ -273,6 +307,9 @@ public class Settings extends BaseSettings {
|
|||
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);
|
||||
public static final LongSetting SB_LOCAL_TIME_SAVED_MILLISECONDS = new LongSetting("sb_local_time_saved_milliseconds", 0L);
|
||||
public static final LongSetting SB_LAST_VIP_CHECK = new LongSetting("sb_last_vip_check", 0L, false, false);
|
||||
public static final BooleanSetting SB_HIDE_EXPORT_WARNING = new BooleanSetting("sb_hide_export_warning", FALSE, false, false);
|
||||
public static final BooleanSetting SB_SEEN_GUIDELINES = new BooleanSetting("sb_seen_guidelines", FALSE, false, false);
|
||||
|
||||
public static final StringSetting SB_CATEGORY_SPONSOR = new StringSetting("sb_sponsor", SKIP_AUTOMATICALLY_ONCE.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_SPONSOR_COLOR = new StringSetting("sb_sponsor_color","#00D400");
|
||||
|
@ -295,11 +332,6 @@ public class Settings extends BaseSettings {
|
|||
public static final StringSetting SB_CATEGORY_UNSUBMITTED = new StringSetting("sb_unsubmitted", SKIP_AUTOMATICALLY.reVancedKeyValue);
|
||||
public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color","#FFFFFF");
|
||||
|
||||
// SB Setting not exported
|
||||
public static final LongSetting SB_LAST_VIP_CHECK = new LongSetting("sb_last_vip_check", 0L, false, false);
|
||||
public static final BooleanSetting SB_HIDE_EXPORT_WARNING = new BooleanSetting("sb_hide_export_warning", FALSE, false, false);
|
||||
public static final BooleanSetting SB_SEEN_GUIDELINES = new BooleanSetting("sb_seen_guidelines", FALSE, false, false);
|
||||
|
||||
static {
|
||||
// region Migration
|
||||
|
||||
|
@ -365,16 +397,9 @@ public class Settings extends BaseSettings {
|
|||
|
||||
|
||||
// Remove any previously saved announcement consumer (a random generated string).
|
||||
Setting.preferences.saveString("revanced_announcement_consumer", null);
|
||||
Setting.preferences.removeKey("revanced_announcement_consumer");
|
||||
|
||||
// Shorts
|
||||
if (DEPRECATED_HIDE_SHORTS.get()) {
|
||||
Logger.printInfo(() -> "Migrating hide Shorts setting");
|
||||
DEPRECATED_HIDE_SHORTS.resetToDefault();
|
||||
HIDE_SHORTS_HOME.save(true);
|
||||
HIDE_SHORTS_SUBSCRIPTIONS.save(true);
|
||||
HIDE_SHORTS_SEARCH.save(true);
|
||||
}
|
||||
migrateOldSettingToNew(HIDE_LOAD_MORE_BUTTON, HIDE_SHOW_MORE_BUTTON);
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import android.util.AttributeSet;
|
|||
/**
|
||||
* Allows tapping the DeArrow about preference to open the DeArrow website.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@SuppressWarnings({"unused", "deprecation"})
|
||||
public class AlternativeThumbnailsAboutDeArrowPreference extends Preference {
|
||||
{
|
||||
setOnPreferenceClickListener(pref -> {
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
package app.revanced.integrations.youtube.settings.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.AttributeSet;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.shared.settings.Setting;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
/**
|
||||
* Shows what thumbnails will be used based on the current settings.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class AlternativeThumbnailsStatusPreference extends Preference {
|
||||
|
||||
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||
// Because this listener may run before the ReVanced settings fragment updates SettingsEnum,
|
||||
// this could show the prior config and not the current.
|
||||
//
|
||||
// Push this call to the end of the main run queue,
|
||||
// so all other listeners are done and SettingsEnum is up to date.
|
||||
Utils.runOnMainThread(this::updateUI);
|
||||
};
|
||||
|
||||
public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public AlternativeThumbnailsStatusPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public AlternativeThumbnailsStatusPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
private void addChangeListener() {
|
||||
Logger.printDebug(() -> "addChangeListener");
|
||||
Setting.preferences.preferences.registerOnSharedPreferenceChangeListener(listener);
|
||||
}
|
||||
|
||||
private void removeChangeListener() {
|
||||
Logger.printDebug(() -> "removeChangeListener");
|
||||
Setting.preferences.preferences.unregisterOnSharedPreferenceChangeListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
|
||||
super.onAttachedToHierarchy(preferenceManager);
|
||||
updateUI();
|
||||
addChangeListener();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPrepareForRemoval() {
|
||||
super.onPrepareForRemoval();
|
||||
removeChangeListener();
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
Logger.printDebug(() -> "updateUI");
|
||||
final boolean usingDeArrow = Settings.ALT_THUMBNAIL_DEARROW.get();
|
||||
final boolean usingVideoStills = Settings.ALT_THUMBNAIL_STILLS.get();
|
||||
|
||||
final String summaryTextKey;
|
||||
if (usingDeArrow && usingVideoStills) {
|
||||
summaryTextKey = "revanced_alt_thumbnail_about_status_dearrow_stills";
|
||||
} else if (usingDeArrow) {
|
||||
summaryTextKey = "revanced_alt_thumbnail_about_status_dearrow";
|
||||
} else if (usingVideoStills) {
|
||||
summaryTextKey = "revanced_alt_thumbnail_about_status_stills";
|
||||
} else {
|
||||
summaryTextKey = "revanced_alt_thumbnail_about_status_disabled";
|
||||
}
|
||||
|
||||
setSummary(str(summaryTextKey));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package app.revanced.integrations.youtube.settings.preference;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import app.revanced.integrations.shared.settings.preference.ReVancedAboutPreference;
|
||||
import app.revanced.integrations.youtube.ThemeHelper;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ReVancedYouTubeAboutPreference extends ReVancedAboutPreference {
|
||||
|
||||
public int getLightColor() {
|
||||
return ThemeHelper.getLightThemeColor();
|
||||
}
|
||||
|
||||
public int getDarkColor() {
|
||||
return ThemeHelper.getDarkThemeColor();
|
||||
}
|
||||
|
||||
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
public ReVancedYouTubeAboutPreference(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
public ReVancedYouTubeAboutPreference(Context context) {
|
||||
super(context);
|
||||
}
|
||||
}
|
|
@ -79,10 +79,9 @@ public class ReturnYouTubeDislikePreferenceFragment extends PreferenceFragment {
|
|||
shortsPreference = new SwitchPreference(context);
|
||||
shortsPreference.setChecked(Settings.RYD_SHORTS.get());
|
||||
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
|
||||
String shortsSummary = str("revanced_ryd_shorts_summary_on",
|
||||
ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
||||
? ""
|
||||
: "\n\n" + str("revanced_ryd_shorts_summary_disclaimer"));
|
||||
String shortsSummary = ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
||||
? str("revanced_ryd_shorts_summary_on")
|
||||
: str("revanced_ryd_shorts_summary_on_disclaimer");
|
||||
shortsPreference.setSummaryOn(shortsSummary);
|
||||
shortsPreference.setSummaryOff(str("revanced_ryd_shorts_summary_off"));
|
||||
shortsPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
|
|
|
@ -100,10 +100,10 @@ public class SponsorBlockPreferenceFragment extends PreferenceFragment {
|
|||
privateUserId.setEnabled(enabled);
|
||||
|
||||
// If the user has a private user id, then include a subtext that mentions not to share it.
|
||||
String exportSummarySubText = SponsorBlockSettings.userHasSBPrivateId()
|
||||
String importExportSummary = SponsorBlockSettings.userHasSBPrivateId()
|
||||
? str("revanced_sb_settings_ie_sum_warning")
|
||||
: "";
|
||||
importExport.setSummary(str("revanced_sb_settings_ie_sum", exportSummarySubText));
|
||||
: str("revanced_sb_settings_ie_sum");
|
||||
importExport.setSummary(importExportSummary);
|
||||
|
||||
apiUrl.setEnabled(enabled);
|
||||
importExport.setEnabled(enabled);
|
||||
|
|
|
@ -1,50 +1,149 @@
|
|||
package app.revanced.integrations.youtube.shared;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton.CREATE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class NavigationBar {
|
||||
private static volatile boolean searchbarIsActive;
|
||||
|
||||
//
|
||||
// Search bar
|
||||
//
|
||||
|
||||
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void searchBarResultsViewLoaded(View searchbarResults) {
|
||||
searchbarResults.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
|
||||
final boolean isActive = searchbarResults.getParent() != null;
|
||||
|
||||
if (searchbarIsActive != isActive) {
|
||||
searchbarIsActive = isActive;
|
||||
Logger.printDebug(() -> "searchbarIsActive: " + isActive);
|
||||
}
|
||||
});
|
||||
searchBarResultsRef = new WeakReference<>(searchbarResults);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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).
|
||||
* Detecting the search is covered by the player can be done by checking {@link PlayerType#isMaximizedOrFullscreen()}.
|
||||
*/
|
||||
public static boolean isSearchBarActive() {
|
||||
return searchbarIsActive;
|
||||
View searchbarResults = searchBarResultsRef.get();
|
||||
return searchbarResults != null && searchbarResults.getParent() != null;
|
||||
}
|
||||
|
||||
//
|
||||
// Navigation bar buttons
|
||||
//
|
||||
|
||||
/**
|
||||
* How long to wait for the set nav button latch to be released. Maximum wait time must
|
||||
* be as small as possible while still allowing enough time for the nav bar to update.
|
||||
*
|
||||
* YT calls it's back button handlers out of order,
|
||||
* and litho starts filtering before the navigation bar is updated.
|
||||
*
|
||||
* Fixing this situation and not needlessly waiting requires somehow
|
||||
* detecting if a back button key-press will cause a tab change.
|
||||
*
|
||||
* Typically after pressing the back button, the time between the first litho event and
|
||||
* when the nav button is updated is about 10-20ms. Using 50-100ms here should be enough time
|
||||
* and not noticeable, since YT typically takes 100-200ms (or more) to update the view anyways.
|
||||
*
|
||||
* This issue can also be avoided on a patch by patch basis, by avoiding calls to
|
||||
* {@link NavigationButton#getSelectedNavigationButton()} unless absolutely necessary.
|
||||
*/
|
||||
private static final long LATCH_AWAIT_TIMEOUT_MILLISECONDS = 75;
|
||||
|
||||
/**
|
||||
* Used as a workaround to fix the issue of YT calling back button handlers out of order.
|
||||
* Used to hold calls to {@link NavigationButton#getSelectedNavigationButton()}
|
||||
* until the current navigation button can be determined.
|
||||
*
|
||||
* Only used when the hardware back button is pressed.
|
||||
*/
|
||||
@Nullable
|
||||
private static volatile CountDownLatch navButtonLatch;
|
||||
|
||||
/**
|
||||
* Map of nav button layout views to Enum type.
|
||||
* No synchronization is needed, and this is always accessed from the main thread.
|
||||
*/
|
||||
private static final Map<View, NavigationButton> viewToButtonMap = new WeakHashMap<>();
|
||||
|
||||
static {
|
||||
// On app startup litho can start before the navigation bar is initialized.
|
||||
// Force it to wait until the nav bar is updated.
|
||||
createNavButtonLatch();
|
||||
}
|
||||
|
||||
private static void createNavButtonLatch() {
|
||||
navButtonLatch = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
private static void releaseNavButtonLatch() {
|
||||
CountDownLatch latch = navButtonLatch;
|
||||
if (latch != null) {
|
||||
navButtonLatch = null;
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
private static void waitForNavButtonLatchIfNeeded() {
|
||||
CountDownLatch latch = navButtonLatch;
|
||||
if (latch == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Utils.isCurrentlyOnMainThread()) {
|
||||
// The latch is released from the main thread, and waiting from the main thread will always timeout.
|
||||
// This situation has only been observed when navigating out of a submenu and not changing tabs.
|
||||
// and for that use case the nav bar does not change so it's safe to return here.
|
||||
Logger.printDebug(() -> "Cannot block main thread waiting for nav button. Using last known navbar button status.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
Logger.printDebug(() -> "Latch wait started");
|
||||
if (latch.await(LATCH_AWAIT_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS)) {
|
||||
// Back button changed the navigation tab.
|
||||
Logger.printDebug(() -> "Latch wait complete");
|
||||
return;
|
||||
}
|
||||
|
||||
// Timeout occurred, and a normal event when pressing the physical back button
|
||||
// does not change navigation tabs.
|
||||
releaseNavButtonLatch(); // Prevent other threads from waiting for no reason.
|
||||
Logger.printDebug(() -> "Latch wait timed out");
|
||||
|
||||
} catch (InterruptedException ex) {
|
||||
Logger.printException(() -> "Latch wait interrupted failure", ex); // Will never happen.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Last YT navigation enum loaded. Not necessarily the active navigation tab.
|
||||
* Always accessed from the main thread.
|
||||
*/
|
||||
@Nullable
|
||||
private static volatile String lastYTNavigationEnumName;
|
||||
private static String lastYTNavigationEnumName;
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setLastAppNavigationEnum(@Nullable Enum ytNavigationEnumName) {
|
||||
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
|
||||
if (ytNavigationEnumName != null) {
|
||||
lastYTNavigationEnumName = ytNavigationEnumName.name();
|
||||
}
|
||||
|
@ -56,21 +155,16 @@ public final class NavigationBar {
|
|||
public static void navigationTabLoaded(final View navigationButtonGroup) {
|
||||
try {
|
||||
String lastEnumName = lastYTNavigationEnumName;
|
||||
|
||||
for (NavigationButton button : NavigationButton.values()) {
|
||||
if (button.ytEnumName.equals(lastEnumName)) {
|
||||
ImageView imageView = Utils.getChildView((ViewGroup) navigationButtonGroup,
|
||||
true, view -> view instanceof ImageView);
|
||||
|
||||
if (imageView != null) {
|
||||
Logger.printDebug(() -> "navigationTabLoaded: " + lastEnumName);
|
||||
|
||||
button.imageViewRef = new WeakReference<>(imageView);
|
||||
navigationTabCreatedCallback(button, navigationButtonGroup);
|
||||
|
||||
return;
|
||||
}
|
||||
Logger.printDebug(() -> "navigationTabLoaded: " + lastEnumName);
|
||||
viewToButtonMap.put(navigationButtonGroup, button);
|
||||
navigationTabCreatedCallback(button, navigationButtonGroup);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Log the unknown tab as exception level, only if debug is enabled.
|
||||
// This is because unknown tabs do no harm, and it's only relevant to developers.
|
||||
if (Settings.DEBUG.get()) {
|
||||
|
@ -98,8 +192,47 @@ public final class NavigationBar {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void navigationTabSelected(View navButtonImageView, boolean isSelected) {
|
||||
try {
|
||||
if (!isSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationButton button = viewToButtonMap.get(navButtonImageView);
|
||||
|
||||
if (button == null) { // An unknown tab was selected.
|
||||
// Show a toast only if debug mode is enabled.
|
||||
if (BaseSettings.DEBUG.get()) {
|
||||
Logger.printException(() -> "Unknown navigation view selected: " + navButtonImageView);
|
||||
}
|
||||
|
||||
NavigationButton.selectedNavigationButton = null;
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationButton.selectedNavigationButton = button;
|
||||
Logger.printDebug(() -> "Changed to navigation button: " + button);
|
||||
|
||||
// Release any threads waiting for the selected nav button.
|
||||
releaseNavButtonLatch();
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "navigationTabSelected failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void onBackPressed(Activity activity) {
|
||||
Logger.printDebug(() -> "Back button pressed");
|
||||
createNavButtonLatch();
|
||||
}
|
||||
|
||||
/** @noinspection EmptyMethod*/
|
||||
private static void navigationTabCreatedCallback(NavigationBar.NavigationButton button, View tabView) {
|
||||
private static void navigationTabCreatedCallback(NavigationButton button, View tabView) {
|
||||
// Code is added during patching.
|
||||
}
|
||||
|
||||
|
@ -108,8 +241,7 @@ public final class NavigationBar {
|
|||
SHORTS("TAB_SHORTS"),
|
||||
/**
|
||||
* Create new video tab.
|
||||
*
|
||||
* {@link #isSelected()} always returns false, even if the create video UI is on screen.
|
||||
* This tab will never be in a selected state, even if the create video UI is on screen.
|
||||
*/
|
||||
CREATE("CREATION_TAB_LARGE"),
|
||||
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS"),
|
||||
|
@ -117,7 +249,7 @@ public final class NavigationBar {
|
|||
* Notifications tab. Only present when
|
||||
* {@link Settings#SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON} is active.
|
||||
*/
|
||||
ACTIVITY("TAB_ACTIVITY"),
|
||||
NOTIFICATIONS("TAB_ACTIVITY"),
|
||||
/**
|
||||
* Library tab when the user is not logged in.
|
||||
*/
|
||||
|
@ -144,41 +276,43 @@ public final class NavigationBar {
|
|||
// The hooked YT code does not use an enum, and a dummy name is used here.
|
||||
LIBRARY_YOU("YOU_LIBRARY_DUMMY_PLACEHOLDER_NAME");
|
||||
|
||||
@Nullable
|
||||
private static volatile NavigationButton selectedNavigationButton;
|
||||
|
||||
/**
|
||||
* This will return null only if the currently selected tab is unknown.
|
||||
* This scenario will only happen if the UI has different tabs due to an A/B user test
|
||||
* or YT abruptly changes the navigation layout for some other reason.
|
||||
*
|
||||
* All code calling this method should handle a null return value.
|
||||
*
|
||||
* <b>Due to issues with how YT processes physical back button events,
|
||||
* this patch uses workarounds that can cause this method to take up to 75ms
|
||||
* if the device back button was recently pressed.</b>
|
||||
*
|
||||
* @return The active navigation tab.
|
||||
* If the user is in the create new video UI, this returns NULL.
|
||||
* If the user is in the upload video UI, this returns tab that is still visually
|
||||
* selected on screen (whatever tab the user was on before tapping the upload button).
|
||||
*/
|
||||
@Nullable
|
||||
public static NavigationButton getSelectedNavigationButton() {
|
||||
for (NavigationButton button : values()) {
|
||||
if (button.isSelected()) return button;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the currently selected tab is a 'You' or library type.
|
||||
* Covers all known app states including incognito mode and version spoofing.
|
||||
*/
|
||||
public static boolean libraryOrYouTabIsSelected() {
|
||||
return LIBRARY_YOU.isSelected() || LIBRARY_PIVOT_UNKNOWN.isSelected()
|
||||
|| LIBRARY_OLD_UI.isSelected() || LIBRARY_INCOGNITO.isSelected()
|
||||
|| LIBRARY_LOGGED_OUT.isSelected();
|
||||
waitForNavButtonLatchIfNeeded();
|
||||
return selectedNavigationButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube enum name for this tab.
|
||||
*/
|
||||
private final String ytEnumName;
|
||||
private volatile WeakReference<ImageView> imageViewRef = new WeakReference<>(null);
|
||||
|
||||
NavigationButton(String ytEnumName) {
|
||||
this.ytEnumName = ytEnumName;
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
ImageView view = imageViewRef.get();
|
||||
return view != null && view.isSelected();
|
||||
public boolean isLibraryOrYouTab() {
|
||||
return this == LIBRARY_YOU || this == LIBRARY_PIVOT_UNKNOWN
|
||||
|| this == LIBRARY_OLD_UI || this == LIBRARY_INCOGNITO
|
||||
|| this == LIBRARY_LOGGED_OUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ package app.revanced.integrations.youtube.sponsorblock;
|
|||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
|
@ -13,13 +12,14 @@ import androidx.annotation.NonNull;
|
|||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.NumberFormat;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.TimeZone;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.patches.VideoInformation;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.sponsorblock.objects.CategoryBehaviour;
|
||||
|
@ -28,25 +28,16 @@ import app.revanced.integrations.youtube.sponsorblock.objects.SponsorSegment;
|
|||
import app.revanced.integrations.youtube.sponsorblock.objects.SponsorSegment.SegmentVote;
|
||||
import app.revanced.integrations.youtube.sponsorblock.requests.SBRequester;
|
||||
import app.revanced.integrations.youtube.sponsorblock.ui.SponsorBlockViewController;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
|
||||
/**
|
||||
* Not thread safe. All fields/methods must be accessed from the main thread.
|
||||
*/
|
||||
public class SponsorBlockUtils {
|
||||
private static final String MANUAL_EDIT_TIME_FORMAT = "HH:mm:ss.SSS";
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private static final SimpleDateFormat manualEditTimeFormatter = new SimpleDateFormat(MANUAL_EDIT_TIME_FORMAT);
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private static final SimpleDateFormat voteSegmentTimeFormatter = new SimpleDateFormat();
|
||||
private static final NumberFormat statsNumberFormatter = NumberFormat.getNumberInstance();
|
||||
static {
|
||||
TimeZone utc = TimeZone.getTimeZone("UTC");
|
||||
manualEditTimeFormatter.setTimeZone(utc);
|
||||
voteSegmentTimeFormatter.setTimeZone(utc);
|
||||
}
|
||||
private static final String LOCKED_COLOR = "#FFC83D";
|
||||
private static final String MANUAL_EDIT_TIME_TEXT_HINT = "hh:mm:ss.sss";
|
||||
private static final Pattern manualEditTimePattern
|
||||
= Pattern.compile("((\\d{1,2}):)?(\\d{1,2}):(\\d{2})(\\.(\\d{1,3}))?");
|
||||
private static final NumberFormat statsNumberFormatter = NumberFormat.getNumberInstance();
|
||||
|
||||
private static long newSponsorSegmentDialogShownMillis;
|
||||
private static long newSponsorSegmentStartMillis = -1;
|
||||
|
@ -131,17 +122,17 @@ public class SponsorBlockUtils {
|
|||
final boolean isStart = DialogInterface.BUTTON_NEGATIVE == which;
|
||||
|
||||
final EditText textView = new EditText(context);
|
||||
textView.setHint(MANUAL_EDIT_TIME_FORMAT);
|
||||
textView.setHint(MANUAL_EDIT_TIME_TEXT_HINT);
|
||||
if (isStart) {
|
||||
if (newSponsorSegmentStartMillis >= 0)
|
||||
textView.setText(manualEditTimeFormatter.format(new Date(newSponsorSegmentStartMillis)));
|
||||
textView.setText(formatSegmentTime(newSponsorSegmentStartMillis));
|
||||
} else {
|
||||
if (newSponsorSegmentEndMillis >= 0)
|
||||
textView.setText(manualEditTimeFormatter.format(new Date(newSponsorSegmentEndMillis)));
|
||||
textView.setText(formatSegmentTime(newSponsorSegmentEndMillis));
|
||||
}
|
||||
|
||||
editByHandSaveDialogListener.settingStart = isStart;
|
||||
editByHandSaveDialogListener.editText = new WeakReference<>(textView);
|
||||
editByHandSaveDialogListener.editTextRef = new WeakReference<>(textView);
|
||||
new AlertDialog.Builder(context)
|
||||
.setTitle(str(isStart ? "revanced_sb_new_segment_time_start" : "revanced_sb_new_segment_time_end"))
|
||||
.setView(textView)
|
||||
|
@ -243,7 +234,7 @@ public class SponsorBlockUtils {
|
|||
new AlertDialog.Builder(SponsorBlockViewController.getOverLaysViewGroupContext())
|
||||
.setTitle(str("revanced_sb_new_segment_title"))
|
||||
.setMessage(str("revanced_sb_new_segment_mark_time_as_question",
|
||||
newSponsorSegmentDialogShownMillis / 60000,
|
||||
newSponsorSegmentDialogShownMillis / 3600000,
|
||||
newSponsorSegmentDialogShownMillis / 1000 % 60,
|
||||
newSponsorSegmentDialogShownMillis % 1000))
|
||||
.setNeutralButton(android.R.string.cancel, null)
|
||||
|
@ -265,15 +256,13 @@ public class SponsorBlockUtils {
|
|||
} else if (!newSponsorSegmentPreviewed && newSponsorSegmentStartMillis != 0) {
|
||||
Utils.showToastLong(str("revanced_sb_new_segment_preview_segment_first"));
|
||||
} else {
|
||||
long length = (newSponsorSegmentEndMillis - newSponsorSegmentStartMillis) / 1000;
|
||||
long start = (newSponsorSegmentStartMillis) / 1000;
|
||||
long end = (newSponsorSegmentEndMillis) / 1000;
|
||||
final long segmentLength = (newSponsorSegmentEndMillis - newSponsorSegmentStartMillis) / 1000;
|
||||
new AlertDialog.Builder(SponsorBlockViewController.getOverLaysViewGroupContext())
|
||||
.setTitle(str("revanced_sb_new_segment_confirm_title"))
|
||||
.setMessage(str("revanced_sb_new_segment_confirm_content",
|
||||
start / 60, start % 60,
|
||||
end / 60, end % 60,
|
||||
length / 60, length % 60))
|
||||
formatSegmentTime(newSponsorSegmentStartMillis),
|
||||
formatSegmentTime(newSponsorSegmentEndMillis),
|
||||
getTimeSavedString(segmentLength)))
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.setPositiveButton(android.R.string.yes, segmentReadyDialogButtonListener)
|
||||
.show();
|
||||
|
@ -295,19 +284,6 @@ public class SponsorBlockUtils {
|
|||
return;
|
||||
}
|
||||
|
||||
// use same time formatting as shown in the video player
|
||||
final long videoLength = VideoInformation.getVideoLength();
|
||||
final String formatPattern;
|
||||
if (videoLength < (10 * 60 * 1000)) {
|
||||
formatPattern = "m:ss.SSS"; // less than 10 minutes
|
||||
} else if (videoLength < (60 * 60 * 1000)) {
|
||||
formatPattern = "mm:ss.SSS"; // less than 1 hour
|
||||
} else if (videoLength < (10 * 60 * 60 * 1000)) {
|
||||
formatPattern = "H:mm:ss.SSS"; // less than 10 hours
|
||||
} else {
|
||||
formatPattern = "HH:mm:ss.SSS"; // why is this on YouTube
|
||||
}
|
||||
voteSegmentTimeFormatter.applyPattern(formatPattern);
|
||||
|
||||
final int numberOfSegments = segments.length;
|
||||
CharSequence[] titles = new CharSequence[numberOfSegments];
|
||||
|
@ -319,9 +295,9 @@ public class SponsorBlockUtils {
|
|||
StringBuilder htmlBuilder = new StringBuilder();
|
||||
htmlBuilder.append(String.format("<b><font color=\"#%06X\">⬤</font> %s<br>",
|
||||
segment.category.color, segment.category.title));
|
||||
htmlBuilder.append(voteSegmentTimeFormatter.format(new Date(segment.start)));
|
||||
htmlBuilder.append(formatSegmentTime(segment.start));
|
||||
if (segment.category != SegmentCategory.HIGHLIGHT) {
|
||||
htmlBuilder.append(" to ").append(voteSegmentTimeFormatter.format(new Date(segment.end)));
|
||||
htmlBuilder.append(" to ").append(formatSegmentTime(segment.end));
|
||||
}
|
||||
htmlBuilder.append("</b>");
|
||||
if (i + 1 != numberOfSegments) // prevents trailing new line after last segment
|
||||
|
@ -367,7 +343,7 @@ public class SponsorBlockUtils {
|
|||
SegmentPlaybackController.addUnsubmittedSegment(
|
||||
new SponsorSegment(SegmentCategory.UNSUBMITTED, null,
|
||||
newSponsorSegmentStartMillis, newSponsorSegmentEndMillis, false));
|
||||
VideoInformation.seekTo(newSponsorSegmentStartMillis - 2500);
|
||||
VideoInformation.seekTo(newSponsorSegmentStartMillis - 2000);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "onPreviewClicked failure", ex);
|
||||
|
@ -408,6 +384,65 @@ public class SponsorBlockUtils {
|
|||
return statsNumberFormatter.format(viewCount);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private static long parseSegmentTime(@NonNull String time) {
|
||||
Matcher matcher = manualEditTimePattern.matcher(time);
|
||||
if (!matcher.matches()) {
|
||||
return -1;
|
||||
}
|
||||
String hoursStr = matcher.group(2); // Hours is optional.
|
||||
String minutesStr = matcher.group(3);
|
||||
String secondsStr = matcher.group(4);
|
||||
String millisecondsStr = matcher.group(6); // Milliseconds is optional.
|
||||
|
||||
try {
|
||||
final int hours = (hoursStr != null) ? Integer.parseInt(hoursStr) : 0;
|
||||
final int minutes = Integer.parseInt(minutesStr);
|
||||
final int seconds = Integer.parseInt(secondsStr);
|
||||
final int milliseconds;
|
||||
if (millisecondsStr != null) {
|
||||
// Pad out with zeros if not all decimal places were used.
|
||||
millisecondsStr = String.format(Locale.US, "%-3s", millisecondsStr).replace(' ', '0');
|
||||
milliseconds = Integer.parseInt(millisecondsStr);
|
||||
} else {
|
||||
milliseconds = 0;
|
||||
}
|
||||
|
||||
return (hours * 3600000L) + (minutes * 60000L) + (seconds * 1000L) + milliseconds;
|
||||
} catch (NumberFormatException ex) {
|
||||
Logger.printInfo(() -> "Time format exception: " + time, ex);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatSegmentTime(long segmentTime) {
|
||||
// Use same time formatting as shown in the video player.
|
||||
final long videoLength = VideoInformation.getVideoLength();
|
||||
|
||||
// Cannot use DateFormatter, as videos over 24 hours will rollover and not display correctly.
|
||||
final long hours = TimeUnit.MILLISECONDS.toHours(segmentTime);
|
||||
final long minutes = TimeUnit.MILLISECONDS.toMinutes(segmentTime) % 60;
|
||||
final long seconds = TimeUnit.MILLISECONDS.toSeconds(segmentTime) % 60;
|
||||
final long milliseconds = segmentTime % 1000;
|
||||
|
||||
final String formatPattern;
|
||||
Object[] formatArgs = {minutes, seconds, milliseconds};
|
||||
|
||||
if (videoLength < (10 * 60 * 1000)) {
|
||||
formatPattern = "%01d:%02d.%03d"; // Less than 10 minutes.
|
||||
} else if (videoLength < (60 * 60 * 1000)) {
|
||||
formatPattern = "%02d:%02d.%03d"; // Less than 1 hour.
|
||||
} else if (videoLength < (10 * 60 * 60 * 1000)) {
|
||||
formatPattern = "%01d:%02d:%02d.%03d"; // Less than 10 hours.
|
||||
formatArgs = new Object[]{hours, minutes, seconds, milliseconds};
|
||||
} else {
|
||||
formatPattern = "%02d:%02d:%02d.%03d"; // Why is this on YouTube?
|
||||
formatArgs = new Object[]{hours, minutes, seconds, milliseconds};
|
||||
}
|
||||
|
||||
return String.format(Locale.US, formatPattern, formatArgs);
|
||||
}
|
||||
|
||||
public static String getTimeSavedString(long totalSecondsSaved) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
Duration duration = Duration.ofSeconds(totalSecondsSaved);
|
||||
|
@ -431,17 +466,24 @@ public class SponsorBlockUtils {
|
|||
|
||||
private static class EditByHandSaveDialogListener implements DialogInterface.OnClickListener {
|
||||
boolean settingStart;
|
||||
WeakReference<EditText> editText;
|
||||
WeakReference<EditText> editTextRef = new WeakReference<>(null);
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try {
|
||||
final EditText editText = this.editText.get();
|
||||
final EditText editText = editTextRef.get();
|
||||
if (editText == null) return;
|
||||
|
||||
long time = (which == DialogInterface.BUTTON_NEUTRAL) ?
|
||||
VideoInformation.getVideoTime() :
|
||||
(Objects.requireNonNull(manualEditTimeFormatter.parse(editText.getText().toString())).getTime());
|
||||
final long time;
|
||||
if (which == DialogInterface.BUTTON_NEUTRAL) {
|
||||
time = VideoInformation.getVideoTime();
|
||||
} else {
|
||||
time = parseSegmentTime(editText.getText().toString());
|
||||
if (time < 0) {
|
||||
Utils.showToastLong(str("revanced_sb_new_segment_edit_by_hand_parse_error"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (settingStart)
|
||||
newSponsorSegmentStartMillis = Math.max(time, 0);
|
||||
|
@ -452,8 +494,6 @@ public class SponsorBlockUtils {
|
|||
editByHandDialogListener.onClick(dialog, settingStart ?
|
||||
DialogInterface.BUTTON_NEGATIVE :
|
||||
DialogInterface.BUTTON_POSITIVE);
|
||||
} catch (ParseException e) {
|
||||
Utils.showToastLong(str("revanced_sb_new_segment_edit_by_hand_parse_error"));
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "EditByHandSaveDialogListener failure", ex);
|
||||
}
|
||||
|
|
|
@ -159,13 +159,13 @@ public class SBRequester {
|
|||
messageToToast = str("revanced_sb_submit_failed_duplicate");
|
||||
break;
|
||||
case 403:
|
||||
messageToToast = str("revanced_sb_submit_failed_forbidden", Requester.parseErrorJsonAndDisconnect(connection));
|
||||
messageToToast = str("revanced_sb_submit_failed_forbidden", Requester.parseErrorStringAndDisconnect(connection));
|
||||
break;
|
||||
case 429:
|
||||
messageToToast = str("revanced_sb_submit_failed_rate_limit");
|
||||
break;
|
||||
case 400:
|
||||
messageToToast = str("revanced_sb_submit_failed_invalid", Requester.parseErrorJsonAndDisconnect(connection));
|
||||
messageToToast = str("revanced_sb_submit_failed_invalid", Requester.parseErrorStringAndDisconnect(connection));
|
||||
break;
|
||||
default:
|
||||
messageToToast = str("revanced_sb_submit_failed_unknown_error", responseCode, connection.getResponseMessage());
|
||||
|
@ -223,7 +223,7 @@ public class SBRequester {
|
|||
break;
|
||||
case 403:
|
||||
Utils.showToastLong(
|
||||
str("revanced_sb_vote_failed_forbidden", Requester.parseErrorJsonAndDisconnect(connection)));
|
||||
str("revanced_sb_vote_failed_forbidden", Requester.parseErrorStringAndDisconnect(connection)));
|
||||
break;
|
||||
default:
|
||||
Utils.showToastLong(
|
||||
|
|
|
@ -104,5 +104,17 @@ class SwipeControlsConfigurationProvider(
|
|||
val shouldSaveAndRestoreBrightness: Boolean
|
||||
get() = Settings.SWIPE_SAVE_AND_RESTORE_BRIGHTNESS.get()
|
||||
|
||||
/**
|
||||
* should auto-brightness be enabled at the lowest value of the brightness gesture
|
||||
*/
|
||||
val shouldLowestValueEnableAutoBrightness: Boolean
|
||||
get() = Settings.SWIPE_LOWEST_VALUE_ENABLE_AUTO_BRIGHTNESS.get()
|
||||
|
||||
/**
|
||||
* variable that stores the brightness gesture value in the settings
|
||||
*/
|
||||
var savedScreenBrightnessValue: Float
|
||||
get() = Settings.SWIPE_BRIGHTNESS_VALUE.get()
|
||||
set(value) = Settings.SWIPE_BRIGHTNESS_VALUE.save(value)
|
||||
//endregion
|
||||
}
|
||||
|
|
|
@ -166,20 +166,31 @@ class SwipeControlsHostActivity : Activity() {
|
|||
contentRoot.addView(overlay)
|
||||
}
|
||||
|
||||
// Flag that indicates whether the brightness has been saved and restored default brightness
|
||||
private var isBrightnessSaved = false
|
||||
|
||||
/**
|
||||
* called when the player type changes
|
||||
*
|
||||
* @param type the new player type
|
||||
*/
|
||||
private fun onPlayerTypeChanged(type: PlayerType) {
|
||||
if (config.shouldSaveAndRestoreBrightness) {
|
||||
when (type) {
|
||||
PlayerType.WATCH_WHILE_FULLSCREEN -> screen?.restore()
|
||||
else -> {
|
||||
screen?.save()
|
||||
screen?.restoreDefaultBrightness()
|
||||
}
|
||||
when {
|
||||
// If saving and restoring brightness is enabled, and the player type is WATCH_WHILE_FULLSCREEN,
|
||||
// and brightness has already been saved, then restore the screen brightness
|
||||
config.shouldSaveAndRestoreBrightness && type == PlayerType.WATCH_WHILE_FULLSCREEN && isBrightnessSaved -> {
|
||||
screen?.restore()
|
||||
isBrightnessSaved = false
|
||||
}
|
||||
// If saving and restoring brightness is enabled, and brightness has not been saved,
|
||||
// then save the current screen state, restore default brightness, and mark brightness as saved
|
||||
config.shouldSaveAndRestoreBrightness && !isBrightnessSaved -> {
|
||||
screen?.save()
|
||||
screen?.restoreDefaultBrightness()
|
||||
isBrightnessSaved = true
|
||||
}
|
||||
// If saving and restoring brightness is disabled, simply keep the default brightness
|
||||
else -> screen?.restoreDefaultBrightness()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,4 +233,4 @@ class SwipeControlsHostActivity : Activity() {
|
|||
var currentHost: WeakReference<SwipeControlsHostActivity> = WeakReference(null)
|
||||
private set
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +1,17 @@
|
|||
package app.revanced.integrations.youtube.swipecontrols.controller
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.WindowManager
|
||||
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsHostActivity
|
||||
import app.revanced.integrations.youtube.swipecontrols.misc.clamp
|
||||
|
||||
/**
|
||||
* controller to adjust the screen brightness level
|
||||
*
|
||||
* @param host the host activity of which the brightness is adjusted
|
||||
* @param host the host activity of which the brightness is adjusted, the main controller instance
|
||||
*/
|
||||
class ScreenBrightnessController(
|
||||
private val host: Activity,
|
||||
val host: SwipeControlsHostActivity,
|
||||
) {
|
||||
/**
|
||||
* screen brightness saved by [save]
|
||||
*/
|
||||
private var savedScreenBrightness: Float? = null
|
||||
|
||||
/**
|
||||
* the current screen brightness in percent, ranging from 0.0 to 100.0
|
||||
|
@ -26,13 +22,6 @@ class ScreenBrightnessController(
|
|||
rawScreenBrightness = (value.toFloat() / 100f).clamp(0f, 1f)
|
||||
}
|
||||
|
||||
/**
|
||||
* restore the screen brightness to the default device brightness
|
||||
*/
|
||||
fun restoreDefaultBrightness() {
|
||||
rawScreenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||
}
|
||||
|
||||
/**
|
||||
* is the screen brightness set to device- default?
|
||||
*/
|
||||
|
@ -40,22 +29,35 @@ class ScreenBrightnessController(
|
|||
get() = (rawScreenBrightness == WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE)
|
||||
|
||||
/**
|
||||
* save the current screen brightness, to be brought back using [restore]
|
||||
* restore the screen brightness to the default device brightness
|
||||
*/
|
||||
fun restoreDefaultBrightness() {
|
||||
rawScreenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
|
||||
}
|
||||
|
||||
// Flag that indicates whether the brightness has been restored
|
||||
private var isBrightnessRestored = false
|
||||
|
||||
/**
|
||||
* save the current screen brightness into settings, to be brought back using [restore]
|
||||
*/
|
||||
fun save() {
|
||||
if (savedScreenBrightness == null) {
|
||||
savedScreenBrightness = rawScreenBrightness
|
||||
if (isBrightnessRestored) {
|
||||
// Saves the current screen brightness value into settings
|
||||
host.config.savedScreenBrightnessValue = rawScreenBrightness
|
||||
// Reset the flag
|
||||
isBrightnessRestored = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restore the screen brightness saved using [save]
|
||||
* restore the screen brightness from settings saved using [save]
|
||||
*/
|
||||
fun restore() {
|
||||
savedScreenBrightness?.let {
|
||||
rawScreenBrightness = it
|
||||
}
|
||||
savedScreenBrightness = null
|
||||
// Restores the screen brightness value from the saved settings
|
||||
rawScreenBrightness = host.config.savedScreenBrightnessValue
|
||||
// Mark that brightness has been restored
|
||||
isBrightnessRestored = true
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -77,12 +77,17 @@ class VolumeAndBrightnessScrollerImpl(
|
|||
),
|
||||
) { _, _, direction ->
|
||||
screenController?.run {
|
||||
if (screenBrightness > 0 || direction > 0) {
|
||||
val shouldAdjustBrightness = if (host.config.shouldLowestValueEnableAutoBrightness) {
|
||||
screenBrightness > 0 || direction > 0
|
||||
} else {
|
||||
screenBrightness >= 0 || direction >= 0
|
||||
}
|
||||
|
||||
if (shouldAdjustBrightness) {
|
||||
screenBrightness += direction
|
||||
} else {
|
||||
restoreDefaultBrightness()
|
||||
}
|
||||
|
||||
overlayController.onBrightnessChanged(screenBrightness)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import app.revanced.integrations.shared.StringRef.str
|
||||
import app.revanced.integrations.shared.Utils
|
||||
import app.revanced.integrations.youtube.swipecontrols.SwipeControlsConfigurationProvider
|
||||
import app.revanced.integrations.youtube.swipecontrols.misc.SwipeControlsOverlay
|
||||
|
@ -122,10 +123,13 @@ class SwipeControlsOverlayLayout(
|
|||
}
|
||||
|
||||
override fun onBrightnessChanged(brightness: Double) {
|
||||
if (brightness > 0) {
|
||||
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)
|
||||
} else {
|
||||
showFeedbackView("AUTO", autoBrightnessIcon)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
org.gradle.parallel = true
|
||||
org.gradle.caching = true
|
||||
android.useAndroidX = true
|
||||
version = 1.5.1-dev.2
|
||||
version = 1.9.0
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
[versions]
|
||||
#noinspection GradleDependency
|
||||
agp = "8.2.2" # 8.3.0 causes java verifier error: https://github.com/ReVanced/revanced-patches/issues/2818
|
||||
annotation = "1.7.1"
|
||||
kotlin = "1.9.22"
|
||||
kotlin = "1.9.23"
|
||||
appcompat = "1.7.0-alpha03"
|
||||
okhttp = "5.0.0-alpha.12"
|
||||
retrofit = "2.9.0"
|
||||
retrofit = "2.11.0"
|
||||
|
||||
[libraries]
|
||||
annotation = { module = "androidx.annotation:annotation", version.ref = "annotation" }
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,6 +4,6 @@
|
|||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"gradle-semantic-release-plugin": "^1.9.1",
|
||||
"semantic-release": "^23.0.0"
|
||||
"semantic-release": "^23.0.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
package com.google.protos.youtube.api.innertube;
|
||||
|
||||
public class InnertubeContext$ClientInfo {
|
||||
public int r;
|
||||
}
|
Loading…
Reference in New Issue