Compare commits
82 Commits
v1.5.0-dev
...
main
Author | SHA1 | Date |
---|---|---|
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 | |
semantic-release-bot | 7ba5581054 | |
LisoUseInAIKyrios | 623d11b8e8 | |
semantic-release-bot | ffccc8a238 | |
oSumAtrIX | fbc8066773 | |
semantic-release-bot | 9f3853ce5e | |
oSumAtrIX | 7760a39602 | |
semantic-release-bot | 633e365466 | |
LisoUseInAIKyrios | 1ee99aa6f0 | |
oSumAtrIX | 4ae7d5b178 | |
semantic-release-bot | 910b03e9a3 | |
LisoUseInAIKyrios | 0cbad98205 | |
oSumAtrIX | 6e947e24c2 | |
oSumAtrIX | 96a1e4680d | |
oSumAtrIX | 46d8ef6f88 | |
oSumAtrIX | b7a8995f79 | |
oSumAtrIX | 59165de801 | |
oSumAtrIX | 2a08e5a98e | |
oSumAtrIX | b945e2f44b | |
oSumAtrIX | bed8f9f640 |
320
CHANGELOG.md
320
CHANGELOG.md
|
@ -1,3 +1,323 @@
|
|||
# [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Navigation buttons:** Hide subscriptions tab ([623d11b](https://github.com/ReVanced/revanced-integrations/commit/623d11b8e8b9f5166b53d8ae195a5d5a6c644661))
|
||||
|
||||
## [1.5.1-dev.1](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0...v1.5.1-dev.1) (2024-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **YouTube - Hide layout components:** Correctly hide community posts ([fbc8066](https://github.com/ReVanced/revanced-integrations/commit/fbc80667735778877f262763d6d53afb1dba3bf6))
|
||||
|
||||
# [1.5.0](https://github.com/ReVanced/revanced-integrations/compare/v1.4.0...v1.5.0) (2024-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Check index of pattern in string instead of the other way around ([96a1e46](https://github.com/ReVanced/revanced-integrations/commit/96a1e4680d23be7154bb83290b1887fcd1a22f53))
|
||||
* Handle custom preferences ([#586](https://github.com/ReVanced/revanced-integrations/issues/586)) ([ad477e4](https://github.com/ReVanced/revanced-integrations/commit/ad477e4859ef69beda297f7a2a6c29a918077628))
|
||||
* Revert AGP dependency update ([ab07a56](https://github.com/ReVanced/revanced-integrations/commit/ab07a563b9ef890dc8a673eeb4268ce1c9f15a69))
|
||||
* **TikTok:** Hook application context earlier to prevent crash ([#588](https://github.com/ReVanced/revanced-integrations/issues/588)) ([9ac2d63](https://github.com/ReVanced/revanced-integrations/commit/9ac2d634897d961eba1b704f2722ea757bb83e0a))
|
||||
* **YouTube - Announcements:** Only compare ID to not show same announcement after a fix-up ([#579](https://github.com/ReVanced/revanced-integrations/issues/579)) ([5d14f53](https://github.com/ReVanced/revanced-integrations/commit/5d14f53acd0b1eabd6951543edd7d7c662b6c502))
|
||||
* **YouTube - Client spoof:** Allow playback for links with timestamp ([#582](https://github.com/ReVanced/revanced-integrations/issues/582)) ([eee3f35](https://github.com/ReVanced/revanced-integrations/commit/eee3f352c59141f47f6bda6c6cd350f1a16f1450))
|
||||
* **YouTube - Disable suggested video end screen:** Disable by default to fix autoplay issues ([#578](https://github.com/ReVanced/revanced-integrations/issues/578)) ([b9c1eec](https://github.com/ReVanced/revanced-integrations/commit/b9c1eec69fab64f213dd77d1f932e3244d81ab2d))
|
||||
* **YouTube - Downloads:** Use new task context ([#583](https://github.com/ReVanced/revanced-integrations/issues/583)) ([468dfac](https://github.com/ReVanced/revanced-integrations/commit/468dfac0544e282658675a8be65b4e43aa351068))
|
||||
* **YouTube - HDR auto brightness:** Remove non functional and obsolete patch ([#585](https://github.com/ReVanced/revanced-integrations/issues/585)) ([b060732](https://github.com/ReVanced/revanced-integrations/commit/b060732e861b011cac8737ed597385a3315f6057))
|
||||
* **YouTube - Hide ads:** Prevent app crash if hiding fullscreen ads is not possible ([#590](https://github.com/ReVanced/revanced-integrations/issues/590)) ([4ec955f](https://github.com/ReVanced/revanced-integrations/commit/4ec955fd0133643826e47be7089fbfa07fd9a089))
|
||||
* **YouTube - Hide layout components:** Correctly hide Join button ([b945e2f](https://github.com/ReVanced/revanced-integrations/commit/b945e2f44b1a62326e6d45345c1467668d803f53))
|
||||
* **YouTube - Hide seekbar:** Use original seekbar color if Theme patch is not included ([#580](https://github.com/ReVanced/revanced-integrations/issues/580)) ([8d48a90](https://github.com/ReVanced/revanced-integrations/commit/8d48a90a8b8bc7ce9e22580b7a66c4c12fd6d54f))
|
||||
* **YouTube - Hide Shorts components:** Correctly hide join button ([b7a8995](https://github.com/ReVanced/revanced-integrations/commit/b7a8995f798e386ee1d9ab5bbd857c1736cc5a29))
|
||||
* **YouTube:** Fix video playback by switching to ReVanced GmsCore vendor ([#589](https://github.com/ReVanced/revanced-integrations/issues/589)) ([6e947e2](https://github.com/ReVanced/revanced-integrations/commit/6e947e24c2ac36e1bddcd25412870a36aa6c85c8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Downloads:** Use external downloader when selecting 'Download' in home feed flyout menu ([#587](https://github.com/ReVanced/revanced-integrations/issues/587)) ([254da31](https://github.com/ReVanced/revanced-integrations/commit/254da31d16c39781f46e1cdea1e9ba22e2fce6d1))
|
||||
* **YouTube - External downloader:** Add ability to use in-app download button ([771a0de](https://github.com/ReVanced/revanced-integrations/commit/771a0de3bc9bec3ec5a8e4f8b02edfa9df7b1997))
|
||||
* **YouTube - Hide layout components:** Filter home/search results by keywords ([#584](https://github.com/ReVanced/revanced-integrations/issues/584)) ([0cbad98](https://github.com/ReVanced/revanced-integrations/commit/0cbad9820577c476f1f29b6ac77611b38afbb950))
|
||||
* **YouTube - Hide Shorts components:** Hide like and dislike buttons ([2a08e5a](https://github.com/ReVanced/revanced-integrations/commit/2a08e5a98e9e9a00bb306313ff487d67c042a92e))
|
||||
* **YouTube - Hide Shorts components:** Hide sound metadata label ([46d8ef6](https://github.com/ReVanced/revanced-integrations/commit/46d8ef6f88bd4c912a45357541291af38b5fc81f))
|
||||
* **YouTube - Hide Shorts components:** Hide title and full video link label ([59165de](https://github.com/ReVanced/revanced-integrations/commit/59165de801a5481fa4055dcf1797fe84dce235c0))
|
||||
* **YouTube - Hide Shorts components:** Selectively hide Shorts for home / subscription / search ([#592](https://github.com/ReVanced/revanced-integrations/issues/592)) ([1ee99aa](https://github.com/ReVanced/revanced-integrations/commit/1ee99aa6f0b4af15eeca25c7e21e8a0f5e9d189a))
|
||||
|
||||
# [1.5.0-dev.10](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.9...v1.5.0-dev.10) (2024-03-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide Shorts components:** Selectively hide Shorts for home / subscription / search ([#592](https://github.com/ReVanced/revanced-integrations/issues/592)) ([1ee99aa](https://github.com/ReVanced/revanced-integrations/commit/1ee99aa6f0b4af15eeca25c7e21e8a0f5e9d189a))
|
||||
|
||||
# [1.5.0-dev.9](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.8...v1.5.0-dev.9) (2024-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Check index of pattern in string instead of the other way around ([96a1e46](https://github.com/ReVanced/revanced-integrations/commit/96a1e4680d23be7154bb83290b1887fcd1a22f53))
|
||||
* **YouTube - Hide layout components:** Correctly hide Join button ([b945e2f](https://github.com/ReVanced/revanced-integrations/commit/b945e2f44b1a62326e6d45345c1467668d803f53))
|
||||
* **YouTube - Hide Shorts components:** Correctly hide join button ([b7a8995](https://github.com/ReVanced/revanced-integrations/commit/b7a8995f798e386ee1d9ab5bbd857c1736cc5a29))
|
||||
* **YouTube:** Fix video playback by switching to ReVanced GmsCore vendor ([#589](https://github.com/ReVanced/revanced-integrations/issues/589)) ([6e947e2](https://github.com/ReVanced/revanced-integrations/commit/6e947e24c2ac36e1bddcd25412870a36aa6c85c8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **YouTube - Hide layout components:** Filter home/search results by keywords ([#584](https://github.com/ReVanced/revanced-integrations/issues/584)) ([0cbad98](https://github.com/ReVanced/revanced-integrations/commit/0cbad9820577c476f1f29b6ac77611b38afbb950))
|
||||
* **YouTube - Hide Shorts components:** Hide like and dislike buttons ([2a08e5a](https://github.com/ReVanced/revanced-integrations/commit/2a08e5a98e9e9a00bb306313ff487d67c042a92e))
|
||||
* **YouTube - Hide Shorts components:** Hide sound metadata label ([46d8ef6](https://github.com/ReVanced/revanced-integrations/commit/46d8ef6f88bd4c912a45357541291af38b5fc81f))
|
||||
* **YouTube - Hide Shorts components:** Hide title and full video link label ([59165de](https://github.com/ReVanced/revanced-integrations/commit/59165de801a5481fa4055dcf1797fe84dce235c0))
|
||||
|
||||
# [1.5.0-dev.8](https://github.com/ReVanced/revanced-integrations/compare/v1.5.0-dev.7...v1.5.0-dev.8) (2024-03-25)
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +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.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
public class GmsCoreSupport {
|
||||
private static final String GMS_CORE_PACKAGE_NAME
|
||||
= 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 void open(String queryOrLink) {
|
||||
Intent intent;
|
||||
try {
|
||||
// Check if queryOrLink is a valid URL.
|
||||
new URL(queryOrLink);
|
||||
|
||||
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);
|
||||
|
||||
// Gracefully exit, otherwise the broken app will continue to run.
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
@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 (vendorGroupId) {
|
||||
case "app.revanced":
|
||||
return "https://github.com/revanced/gmscore/releases/latest";
|
||||
default:
|
||||
return vendorGroupId + ".android.gms";
|
||||
}
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
private static String getGmsCoreVendorGroupId() {
|
||||
return "app.revanced";
|
||||
}
|
||||
}
|
|
@ -35,6 +35,7 @@ import java.util.concurrent.ThreadPoolExecutor;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||
import app.revanced.integrations.shared.settings.preference.ReVancedAboutPreference;
|
||||
import kotlin.text.Regex;
|
||||
|
||||
public class Utils {
|
||||
|
@ -47,30 +48,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -185,6 +200,7 @@ public class Utils {
|
|||
}
|
||||
|
||||
public static int getResourceColor(@NonNull String resourceIdentifierName) throws Resources.NotFoundException {
|
||||
//noinspection deprecation
|
||||
return getContext().getResources().getColor(getResourceIdentifier(resourceIdentifierName, "color"));
|
||||
}
|
||||
|
||||
|
@ -196,18 +212,29 @@ public class Utils {
|
|||
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
||||
}
|
||||
|
||||
public interface MatchFilter<T> {
|
||||
boolean matches(T object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searchRecursively If children ViewGroups should also be
|
||||
* recursively searched using depth first search.
|
||||
* @return The first child view that matches the filter.
|
||||
*/
|
||||
@Nullable
|
||||
public static <T extends View> T getChildView(@NonNull ViewGroup viewGroup, @NonNull MatchFilter filter) {
|
||||
public static <T extends View> T getChildView(@NonNull ViewGroup viewGroup, boolean searchRecursively,
|
||||
@NonNull MatchFilter<View> filter) {
|
||||
for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) {
|
||||
View childAt = viewGroup.getChildAt(i);
|
||||
//noinspection unchecked
|
||||
if (filter.matches(childAt)) {
|
||||
//noinspection unchecked
|
||||
return (T) childAt;
|
||||
}
|
||||
// Must do recursive after filter check, in case the filter is looking for a ViewGroup.
|
||||
if (searchRecursively && childAt instanceof ViewGroup) {
|
||||
T match = getChildView((ViewGroup) childAt, true, filter);
|
||||
if (match != null) return match;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -223,10 +250,6 @@ public class Utils {
|
|||
System.exit(0);
|
||||
}
|
||||
|
||||
public interface MatchFilter<T> {
|
||||
boolean matches(T object);
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
if (context == null) {
|
||||
Logger.initializationException(Utils.class, "Context is null, returning null!", null);
|
||||
|
@ -316,7 +339,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);
|
||||
|
@ -438,11 +461,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)) {
|
||||
|
@ -450,7 +470,7 @@ public class Utils {
|
|||
}
|
||||
}
|
||||
}
|
||||
return UNSORTED;
|
||||
return defaultSort;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -472,19 +492,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;
|
||||
|
@ -504,8 +531,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,5 +1,9 @@
|
|||
package app.revanced.integrations.youtube;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
||||
|
||||
private static final class ByteTrieNode extends TrieNode<byte[]> {
|
||||
|
@ -24,18 +28,18 @@ public final class ByteTrieSearch extends TrieSearch<byte[]> {
|
|||
}
|
||||
|
||||
/**
|
||||
* @return If the pattern is valid to add to this instance.
|
||||
* Helper method for the common usage of converting Strings to raw UTF-8 bytes.
|
||||
*/
|
||||
public static boolean isValidPattern(byte[] pattern) {
|
||||
for (byte b : pattern) {
|
||||
if (TrieNode.isInvalidRange((char) b)) {
|
||||
return false;
|
||||
}
|
||||
public static byte[][] convertStringsToBytes(String... strings) {
|
||||
final int length = strings.length;
|
||||
byte[][] replacement = new byte[length][];
|
||||
for (int i = 0; i < length; i++) {
|
||||
replacement[i] = strings[i].getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
return true;
|
||||
return replacement;
|
||||
}
|
||||
|
||||
public ByteTrieSearch() {
|
||||
super(new ByteTrieNode());
|
||||
public ByteTrieSearch(@NonNull byte[]... patterns) {
|
||||
super(new ByteTrieNode(), patterns);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package app.revanced.integrations.youtube;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Text pattern searching using a prefix tree (trie).
|
||||
*/
|
||||
|
@ -26,19 +28,7 @@ public final class StringTrieSearch extends TrieSearch<String> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the pattern is valid to add to this instance.
|
||||
*/
|
||||
public static boolean isValidPattern(String pattern) {
|
||||
for (int i = 0, length = pattern.length(); i < length; i++) {
|
||||
if (TrieNode.isInvalidRange(pattern.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public StringTrieSearch() {
|
||||
super(new StringTrieNode());
|
||||
public StringTrieSearch(@NonNull String... patterns) {
|
||||
super(new StringTrieNode(), patterns);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
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.
|
||||
*/
|
||||
public static void setTheme(Enum<?> value) {
|
||||
final int newOrdinalValue = value.ordinal();
|
||||
if (themeValue != newOrdinalValue) {
|
||||
themeValue = newOrdinalValue;
|
||||
Logger.printDebug(() -> "Theme value: " + newOrdinalValue);
|
||||
|
@ -26,4 +35,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 "@android:color/black";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The dark theme color as specified by the Theme patch (if included),
|
||||
* or the Android color of black.
|
||||
*/
|
||||
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 "@android:color/white";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The light theme color as specified by the Theme patch (if included),
|
||||
* or the Android color of white.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,6 @@ import java.util.Objects;
|
|||
/**
|
||||
* Searches for a group of different patterns using a trie (prefix tree).
|
||||
* Can significantly speed up searching for multiple patterns.
|
||||
*
|
||||
* Currently only supports ASCII non-control characters (letters/numbers/symbols).
|
||||
* But could be modified to also support UTF-8 unicode.
|
||||
*/
|
||||
public abstract class TrieSearch<T> {
|
||||
|
||||
|
@ -45,14 +42,14 @@ public abstract class TrieSearch<T> {
|
|||
*/
|
||||
private static final class TrieCompressedPath<T> {
|
||||
final T pattern;
|
||||
final int patternLength;
|
||||
final int patternStartIndex;
|
||||
final int patternLength;
|
||||
final TriePatternMatchedCallback<T> callback;
|
||||
|
||||
TrieCompressedPath(T pattern, int patternLength, int patternStartIndex, TriePatternMatchedCallback<T> callback) {
|
||||
TrieCompressedPath(T pattern, int patternStartIndex, int patternLength, TriePatternMatchedCallback<T> callback) {
|
||||
this.pattern = pattern;
|
||||
this.patternLength = patternLength;
|
||||
this.patternStartIndex = patternStartIndex;
|
||||
this.patternLength = patternLength;
|
||||
this.callback = callback;
|
||||
}
|
||||
boolean matches(TrieNode<T> enclosingNode, // Used only for the get character method.
|
||||
|
@ -76,19 +73,10 @@ public abstract class TrieSearch<T> {
|
|||
*/
|
||||
private static final char ROOT_NODE_CHARACTER_VALUE = 0; // ASCII null character.
|
||||
|
||||
// Support only ASCII letters/numbers/symbols and filter out all control characters.
|
||||
private static final char MIN_VALID_CHAR = 32; // Space character.
|
||||
private static final char MAX_VALID_CHAR = 126; // 127 = delete character.
|
||||
|
||||
/**
|
||||
* How much to expand the children array when resizing.
|
||||
*/
|
||||
private static final int CHILDREN_ARRAY_INCREASE_SIZE_INCREMENT = 2;
|
||||
private static final int CHILDREN_ARRAY_MAX_SIZE = MAX_VALID_CHAR - MIN_VALID_CHAR + 1;
|
||||
|
||||
static boolean isInvalidRange(char character) {
|
||||
return character < MIN_VALID_CHAR || character > MAX_VALID_CHAR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Character this node represents.
|
||||
|
@ -144,11 +132,11 @@ public abstract class TrieSearch<T> {
|
|||
|
||||
/**
|
||||
* @param pattern Pattern to add.
|
||||
* @param patternLength Length of the pattern.
|
||||
* @param patternIndex Current recursive index of the pattern.
|
||||
* @param patternLength Length of the pattern.
|
||||
* @param callback Callback, where a value of NULL indicates to always accept a pattern match.
|
||||
*/
|
||||
private void addPattern(@NonNull T pattern, int patternLength, int patternIndex,
|
||||
private void addPattern(@NonNull T pattern, int patternIndex, int patternLength,
|
||||
@Nullable TriePatternMatchedCallback<T> callback) {
|
||||
if (patternIndex == patternLength) { // Reached the end of the pattern.
|
||||
if (endOfPatternCallback == null) {
|
||||
|
@ -165,16 +153,13 @@ public abstract class TrieSearch<T> {
|
|||
children = new TrieNode[1];
|
||||
TrieCompressedPath<T> temp = leaf;
|
||||
leaf = null;
|
||||
addPattern(temp.pattern, temp.patternLength, temp.patternStartIndex, temp.callback);
|
||||
addPattern(temp.pattern, temp.patternStartIndex, temp.patternLength, temp.callback);
|
||||
// Continue onward and add the parameter pattern.
|
||||
} else if (children == null) {
|
||||
leaf = new TrieCompressedPath<>(pattern, patternLength, patternIndex, callback);
|
||||
leaf = new TrieCompressedPath<>(pattern, patternIndex, patternLength, callback);
|
||||
return;
|
||||
}
|
||||
final char character = getCharValue(pattern, patternIndex);
|
||||
if (isInvalidRange(character)) {
|
||||
throw new IllegalArgumentException("invalid character at index " + patternIndex + ": " + pattern);
|
||||
}
|
||||
final int arrayIndex = hashIndexForTableSize(children.length, character);
|
||||
TrieNode<T> child = children[arrayIndex];
|
||||
if (child == null) {
|
||||
|
@ -185,12 +170,11 @@ public abstract class TrieSearch<T> {
|
|||
child = createNode(character);
|
||||
expandChildArray(child);
|
||||
}
|
||||
child.addPattern(pattern, patternLength, patternIndex + 1, callback);
|
||||
child.addPattern(pattern, patternIndex + 1, patternLength, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the children table until all nodes hash to exactly one array index.
|
||||
* Worse case, this will resize the array to {@link #CHILDREN_ARRAY_MAX_SIZE} elements.
|
||||
*/
|
||||
private void expandChildArray(TrieNode<T> child) {
|
||||
int replacementArraySize = Objects.requireNonNull(children).length;
|
||||
|
@ -209,7 +193,6 @@ public abstract class TrieSearch<T> {
|
|||
}
|
||||
}
|
||||
if (collision) {
|
||||
if (replacementArraySize > CHILDREN_ARRAY_MAX_SIZE) throw new IllegalStateException();
|
||||
continue;
|
||||
}
|
||||
children = replacement;
|
||||
|
@ -232,22 +215,23 @@ public abstract class TrieSearch<T> {
|
|||
|
||||
/**
|
||||
* This method is static and uses a loop to avoid all recursion.
|
||||
* This is done for performance since the JVM does not do tail recursion optimization.
|
||||
* This is done for performance since the JVM does not optimize tail recursion.
|
||||
*
|
||||
* @param startNode Node to start the search from.
|
||||
* @param searchText Text to search for patterns in.
|
||||
* @param searchTextLength Length of the search text.
|
||||
* @param searchTextIndex Current recursive search text index. Also, the end index of the current pattern match.
|
||||
* @param searchTextIndex Start index, inclusive.
|
||||
* @param searchTextEndIndex End index, exclusive.
|
||||
* @return If any pattern matches, and it's associated callback halted the search.
|
||||
*/
|
||||
private static <T> boolean matches(final TrieNode<T> startNode, final T searchText, final int searchTextLength,
|
||||
int searchTextIndex, final Object callbackParameter) {
|
||||
private static <T> boolean matches(final TrieNode<T> startNode, final T searchText,
|
||||
int searchTextIndex, final int searchTextEndIndex,
|
||||
final Object callbackParameter) {
|
||||
TrieNode<T> node = startNode;
|
||||
int currentMatchLength = 0;
|
||||
|
||||
while (true) {
|
||||
TrieCompressedPath<T> leaf = node.leaf;
|
||||
if (leaf != null && leaf.matches(node, searchText, searchTextLength, searchTextIndex, callbackParameter)) {
|
||||
if (leaf != null && leaf.matches(startNode, searchText, searchTextEndIndex, searchTextIndex, callbackParameter)) {
|
||||
return true; // Leaf exists and it matched the search text.
|
||||
}
|
||||
List<TriePatternMatchedCallback<T>> endOfPatternCallback = node.endOfPatternCallback;
|
||||
|
@ -266,7 +250,7 @@ public abstract class TrieSearch<T> {
|
|||
if (children == null) {
|
||||
return false; // Reached a graph end point and there's no further patterns to search.
|
||||
}
|
||||
if (searchTextIndex == searchTextLength) {
|
||||
if (searchTextIndex == searchTextEndIndex) {
|
||||
return false; // Reached end of the search text and found no matches.
|
||||
}
|
||||
|
||||
|
@ -323,8 +307,10 @@ public abstract class TrieSearch<T> {
|
|||
*/
|
||||
private final List<T> patterns = new ArrayList<>();
|
||||
|
||||
TrieSearch(@NonNull TrieNode<T> root) {
|
||||
@SafeVarargs
|
||||
TrieSearch(@NonNull TrieNode<T> root, @NonNull T... patterns) {
|
||||
this.root = Objects.requireNonNull(root);
|
||||
addPatterns(patterns);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
|
@ -355,7 +341,7 @@ public abstract class TrieSearch<T> {
|
|||
if (patternLength == 0) return; // Nothing to match
|
||||
|
||||
patterns.add(pattern);
|
||||
root.addPattern(pattern, patternLength, 0, callback);
|
||||
root.addPattern(pattern, 0, patternLength, callback);
|
||||
}
|
||||
|
||||
public final boolean matches(@NonNull T textToSearch) {
|
||||
|
@ -398,7 +384,7 @@ public abstract class TrieSearch<T> {
|
|||
return false; // No patterns were added.
|
||||
}
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
if (TrieNode.matches(root, textToSearch, endIndex, i, callbackParameter)) return true;
|
||||
if (TrieNode.matches(root, textToSearch, i, endIndex, callbackParameter)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -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,78 +0,0 @@
|
|||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
import android.app.SearchManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
|
||||
/**
|
||||
* @noinspection unused
|
||||
*/
|
||||
public class GmsCoreSupport {
|
||||
private static final String GMS_CORE_PACKAGE_NAME
|
||||
= getGmsCoreVendor() + ".android.gms";
|
||||
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());
|
||||
|
||||
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"));
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
try (var client = context.getContentResolver().acquireContentProviderClient(GMS_CORE_PROVIDER)) {
|
||||
if (client != null) return;
|
||||
|
||||
Logger.printInfo(() -> "GmsCore is not running in the background");
|
||||
search(context, DONT_KILL_MY_APP_LINK, str("gms_core_not_running_warning"));
|
||||
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getGmsCoreDownloadLink() {
|
||||
final var vendor = getGmsCoreVendor();
|
||||
switch (vendor) {
|
||||
case "com.mgoogle":
|
||||
return "https://github.com/TeamVanced/VancedMicroG/releases/latest";
|
||||
case "app.revanced":
|
||||
return "https://github.com/revanced/gmscore/releases/latest";
|
||||
default:
|
||||
return vendor + ".android.gms";
|
||||
}
|
||||
}
|
||||
|
||||
// Modified by a patch. Do not touch.
|
||||
private static String getGmsCoreVendor() {
|
||||
return "app.revanced";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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,40 +1,43 @@
|
|||
package app.revanced.integrations.youtube.patches;
|
||||
|
||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public final class NavigationButtonsPatch {
|
||||
public static Enum lastNavigationButton;
|
||||
|
||||
public static void hideCreateButton(final View view) {
|
||||
view.setVisibility(Settings.HIDE_CREATE_BUTTON.get() ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
private static final Map<NavigationButton, Boolean> shouldHideMap = new EnumMap<>(NavigationButton.class) {
|
||||
{
|
||||
put(NavigationButton.HOME, Settings.HIDE_HOME_BUTTON.get());
|
||||
put(NavigationButton.CREATE, Settings.HIDE_CREATE_BUTTON.get());
|
||||
put(NavigationButton.SHORTS, Settings.HIDE_SHORTS_BUTTON.get());
|
||||
put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
||||
}
|
||||
};
|
||||
|
||||
private static final Boolean SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON
|
||||
= Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static boolean switchCreateWithNotificationButton() {
|
||||
return Settings.SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON.get();
|
||||
return SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON;
|
||||
}
|
||||
|
||||
public static void hideButton(final View buttonView) {
|
||||
if (lastNavigationButton == null) return;
|
||||
|
||||
for (NavigationButton button : NavigationButton.values())
|
||||
if (button.name.equals(lastNavigationButton.name()))
|
||||
if (button.enabled) buttonView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private enum NavigationButton {
|
||||
HOME("PIVOT_HOME", Settings.HIDE_HOME_BUTTON.get()),
|
||||
SHORTS("TAB_SHORTS", Settings.HIDE_SHORTS_BUTTON.get()),
|
||||
SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS", Settings.HIDE_SUBSCRIPTIONS_BUTTON.get());
|
||||
private final boolean enabled;
|
||||
private final String name;
|
||||
|
||||
NavigationButton(final String name, final boolean enabled) {
|
||||
this.name = name;
|
||||
this.enabled = enabled;
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void navigationTabCreated(NavigationButton button, View tabView) {
|
||||
if (Boolean.TRUE.equals(shouldHideMap.get(button))) {
|
||||
tabView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
@ -18,7 +19,6 @@ import java.net.HttpURLConnection;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
import static android.text.Html.FROM_HTML_MODE_COMPACT;
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
@ -26,8 +26,6 @@ import static app.revanced.integrations.youtube.patches.announcements.requests.A
|
|||
|
||||
@SuppressWarnings("unused")
|
||||
public final class AnnouncementsPatch {
|
||||
private final static String CONSUMER = getOrSetConsumer();
|
||||
|
||||
private AnnouncementsPatch() {
|
||||
}
|
||||
|
||||
|
@ -41,7 +39,7 @@ public final class AnnouncementsPatch {
|
|||
Utils.runOnBackgroundThread(() -> {
|
||||
try {
|
||||
HttpURLConnection connection = AnnouncementsRoutes.getAnnouncementsConnectionFromRoute(
|
||||
GET_LATEST_ANNOUNCEMENT, CONSUMER, Locale.getDefault().toLanguageTag());
|
||||
GET_LATEST_ANNOUNCEMENT, Locale.getDefault().toLanguageTag());
|
||||
|
||||
Logger.printDebug(() -> "Get latest announcement route connection url: " + connection.getURL());
|
||||
|
||||
|
@ -63,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.
|
||||
|
@ -118,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)
|
||||
|
@ -139,15 +137,6 @@ public final class AnnouncementsPatch {
|
|||
});
|
||||
}
|
||||
|
||||
private static String getOrSetConsumer() {
|
||||
final var consumer = Settings.ANNOUNCEMENT_CONSUMER.get();
|
||||
if (!consumer.isEmpty()) return consumer;
|
||||
|
||||
final var uuid = UUID.randomUUID().toString();
|
||||
Settings.ANNOUNCEMENT_CONSUMER.save(uuid);
|
||||
return uuid;
|
||||
}
|
||||
|
||||
// TODO: Use better icons.
|
||||
private enum Level {
|
||||
INFO(android.R.drawable.ic_dialog_info),
|
||||
|
|
|
@ -14,7 +14,7 @@ public class AnnouncementsRoutes {
|
|||
/**
|
||||
* 'language' parameter is IETF format (for USA it would be 'en-us').
|
||||
*/
|
||||
public static final Route GET_LATEST_ANNOUNCEMENT = new Route(GET, "/announcements/youtube/latest?consumer={consumer}&language={language}");
|
||||
public static final Route GET_LATEST_ANNOUNCEMENT = new Route(GET, "/announcements/youtube/latest?language={language}");
|
||||
|
||||
private AnnouncementsRoutes() {
|
||||
}
|
||||
|
|
|
@ -129,8 +129,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;
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
@ -89,8 +85,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) {
|
||||
|
|
|
@ -10,14 +10,12 @@ 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;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.ByteTrieSearch;
|
||||
import app.revanced.integrations.youtube.StringTrieSearch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
|
||||
/**
|
||||
|
@ -30,10 +28,6 @@ final class CustomFilter extends Filter {
|
|||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
|
||||
}
|
||||
|
||||
private static void showInvalidCharactersToast(@NonNull String expression) {
|
||||
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_characters", expression));
|
||||
}
|
||||
|
||||
private static class CustomFilterGroup extends StringFilterGroup {
|
||||
/**
|
||||
* Optional character for the path that indicates the custom filter path must match the start.
|
||||
|
@ -47,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();
|
||||
|
@ -73,7 +67,7 @@ final class CustomFilter extends Filter {
|
|||
Matcher matcher = pattern.matcher(expression);
|
||||
if (!matcher.find()) {
|
||||
showInvalidSyntaxToast(expression);
|
||||
return null;
|
||||
continue;
|
||||
}
|
||||
|
||||
final String mapKey = matcher.group(1);
|
||||
|
@ -84,13 +78,7 @@ final class CustomFilter extends Filter {
|
|||
|
||||
if (path.isBlank() || (hasBufferSymbol && bufferString.isBlank())) {
|
||||
showInvalidSyntaxToast(expression);
|
||||
return null;
|
||||
}
|
||||
if (!StringTrieSearch.isValidPattern(path)
|
||||
|| (hasBufferSymbol && !StringTrieSearch.isValidPattern(bufferString))) {
|
||||
// Currently only ASCII is allowed.
|
||||
showInvalidCharactersToast(path);
|
||||
return null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use one group object for all expressions with the same path.
|
||||
|
@ -149,11 +137,6 @@ final class CustomFilter extends Filter {
|
|||
|
||||
public CustomFilter() {
|
||||
Collection<CustomFilterGroup> groups = CustomFilterGroup.parseCustomFilterGroups();
|
||||
if (groups == null) {
|
||||
Settings.CUSTOM_FILTER_STRINGS.resetToDefault();
|
||||
Utils.showToastLong(str("revanced_custom_filter_toast_reset"));
|
||||
groups = Objects.requireNonNull(CustomFilterGroup.parseCustomFilterGroups());
|
||||
}
|
||||
|
||||
if (!groups.isEmpty()) {
|
||||
CustomFilterGroup[] groupsArray = groups.toArray(new CustomFilterGroup[0]);
|
||||
|
@ -163,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) {
|
||||
|
|
|
@ -0,0 +1,284 @@
|
|||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
import static app.revanced.integrations.youtube.ByteTrieSearch.convertStringsToBytes;
|
||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.youtube.ByteTrieSearch;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.shared.NavigationBar;
|
||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Allows hiding home feed and search results based on keywords and/or channel names.
|
||||
*
|
||||
* Limitations:
|
||||
* - Searching for a keyword phrase will give no search results.
|
||||
* This is because the buffer for each video contains the text the user searched for, and everything
|
||||
* will be filtered away (even if that video title/channel does not contain any keywords).
|
||||
* - Filtering a channel name can still show Shorts from that channel in the search results.
|
||||
* The most common Shorts layouts do not include the channel name, so they will not be filtered.
|
||||
* - Some layout component residue will remain, such as the video chapter previews for some search results.
|
||||
* These components do not include the video title or channel name, and they
|
||||
* appear outside the filtered components so they are not caught.
|
||||
* - Keywords are case sensitive, but some casing variation is manually added.
|
||||
* (ie: "mr beast" automatically filters "Mr Beast" and "MR BEAST").
|
||||
* - Keywords present in the layout or video data cannot be used as filters, otherwise all videos
|
||||
* will always be hidden. This patch checks for some words of these words.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
final class KeywordContentFilter extends Filter {
|
||||
|
||||
/**
|
||||
* Minimum keyword/phrase length to prevent excessively broad content filtering.
|
||||
*/
|
||||
private static final int MINIMUM_KEYWORD_LENGTH = 3;
|
||||
|
||||
/**
|
||||
* Strings found in the buffer for every videos.
|
||||
* Full strings should be specified, as they are compared using {@link String#contains(CharSequence)}.
|
||||
*
|
||||
* This list does not include every common buffer string, and this can be added/changed as needed.
|
||||
* Words must be entered with the exact casing as found in the buffer.
|
||||
*/
|
||||
private static final String[] STRINGS_IN_EVERY_BUFFER = {
|
||||
// Video playback data.
|
||||
"https://i.ytimg.com/vi/", // Thumbnail url.
|
||||
"sddefault.jpg", // More video sizes exist, but for most devices only these 2 are used.
|
||||
"hqdefault.webp",
|
||||
"googlevideo.com/initplayback?source=youtube", // Video url.
|
||||
"ANDROID", // Video url parameter.
|
||||
// Video decoders.
|
||||
"OMX.ffmpeg.vp9.decoder",
|
||||
"OMX.Intel.sw_vd.vp9",
|
||||
"OMX.sprd.av1.decoder",
|
||||
"OMX.MTK.VIDEO.DECODER.SW.VP9",
|
||||
"c2.android.av1.decoder",
|
||||
"c2.mtk.sw.vp9.decoder",
|
||||
// User analytics.
|
||||
"https://ad.doubleclick.net/ddm/activity/",
|
||||
"DEVICE_ADVERTISER_ID_FOR_CONVERSION_TRACKING",
|
||||
// Litho components frequently found in the buffer that belong to the path filter items.
|
||||
"metadata.eml",
|
||||
"thumbnail.eml",
|
||||
"avatar.eml",
|
||||
"overflow_button.eml",
|
||||
};
|
||||
|
||||
/**
|
||||
* Substrings that are always first in the identifier.
|
||||
*/
|
||||
private final StringFilterGroup startsWithFilter = new StringFilterGroup(
|
||||
null, // Multiple settings are used and must be individually checked if active.
|
||||
"home_video_with_context.eml",
|
||||
"search_video_with_context.eml",
|
||||
"video_with_context.eml", // Subscription tab videos.
|
||||
"related_video_with_context.eml",
|
||||
"compact_video.eml",
|
||||
"inline_shorts",
|
||||
"shorts_video_cell",
|
||||
"shorts_pivot_item.eml"
|
||||
);
|
||||
|
||||
/**
|
||||
* Substrings that are never at the start of the path.
|
||||
*/
|
||||
private final StringFilterGroup containsFilter = new StringFilterGroup(
|
||||
null,
|
||||
"modern_type_shelf_header_content.eml",
|
||||
"shorts_lockup_cell.eml" // Part of 'shorts_shelf_carousel.eml'
|
||||
);
|
||||
|
||||
/**
|
||||
* The last value of {@link Settings#HIDE_KEYWORD_CONTENT_PHRASES}
|
||||
* parsed and loaded into {@link #bufferSearch}.
|
||||
* Allows changing the keywords without restarting the app.
|
||||
*/
|
||||
private volatile String lastKeywordPhrasesParsed;
|
||||
|
||||
private volatile ByteTrieSearch bufferSearch;
|
||||
|
||||
private static boolean hideKeywordSettingIsActive() {
|
||||
// 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 Settings.HIDE_KEYWORD_CONTENT_HOME.get();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change first letter of the first word to use title case.
|
||||
*/
|
||||
private static String titleCaseFirstWordOnly(String sentence) {
|
||||
if (sentence.isEmpty()) {
|
||||
return sentence;
|
||||
}
|
||||
final int firstCodePoint = sentence.codePointAt(0);
|
||||
// In some non English languages title case is different than upper case.
|
||||
return new StringBuilder()
|
||||
.appendCodePoint(Character.toTitleCase(firstCodePoint))
|
||||
.append(sentence, Character.charCount(firstCodePoint), sentence.length())
|
||||
.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uppercase the first letter of each word.
|
||||
*/
|
||||
private static String capitalizeAllFirstLetters(String sentence) {
|
||||
if (sentence.isEmpty()) {
|
||||
return sentence;
|
||||
}
|
||||
final int delimiter = ' ';
|
||||
// Use code points and not characters to handle unicode surrogates.
|
||||
int[] codePoints = sentence.codePoints().toArray();
|
||||
boolean capitalizeNext = true;
|
||||
for (int i = 0, length = codePoints.length; i < length; i++) {
|
||||
final int codePoint = codePoints[i];
|
||||
if (codePoint == delimiter) {
|
||||
capitalizeNext = true;
|
||||
} else if (capitalizeNext) {
|
||||
codePoints[i] = Character.toUpperCase(codePoint);
|
||||
capitalizeNext = false;
|
||||
}
|
||||
}
|
||||
return new String(codePoints, 0, codePoints.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return If the phrase will will hide all videos. Not an exhaustive check.
|
||||
*/
|
||||
private static boolean phrasesWillHideAllVideos(@NonNull String[] phrases) {
|
||||
for (String commonString : STRINGS_IN_EVERY_BUFFER) {
|
||||
if (Utils.containsAny(commonString, phrases)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
ByteTrieSearch search = new ByteTrieSearch();
|
||||
String[] split = rawKeywords.split("\n");
|
||||
if (split.length != 0) {
|
||||
// Linked Set so log statement are more organized and easier to read.
|
||||
Set<String> keywords = new LinkedHashSet<>(10 * split.length);
|
||||
|
||||
for (String phrase : split) {
|
||||
// Remove any trailing white space the user may have accidentally included.
|
||||
phrase = phrase.stripTrailing();
|
||||
if (phrase.isBlank()) continue;
|
||||
|
||||
if (phrase.length() < MINIMUM_KEYWORD_LENGTH) {
|
||||
// Do not reset the setting. Keep the invalid keywords so the user can fix the mistake.
|
||||
Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_length", phrase, MINIMUM_KEYWORD_LENGTH));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add common casing that might appear.
|
||||
//
|
||||
// This could be simplified by adding case insensitive search to the prefix search,
|
||||
// which is very simple to add to StringTreSearch for Unicode and ByteTrieSearch for ASCII.
|
||||
//
|
||||
// But to support Unicode with ByteTrieSearch would require major changes because
|
||||
// UTF-8 characters can be different byte lengths, which does
|
||||
// not allow comparing two different byte arrays using simple plain array indexes.
|
||||
//
|
||||
// Instead add all common case variations of the words.
|
||||
String[] phraseVariations = {
|
||||
phrase,
|
||||
phrase.toLowerCase(),
|
||||
titleCaseFirstWordOnly(phrase),
|
||||
capitalizeAllFirstLetters(phrase),
|
||||
phrase.toUpperCase()
|
||||
};
|
||||
if (phrasesWillHideAllVideos(phraseVariations)) {
|
||||
Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_common", phrase));
|
||||
continue;
|
||||
}
|
||||
|
||||
keywords.addAll(Arrays.asList(phraseVariations));
|
||||
}
|
||||
|
||||
search.addPatterns(convertStringsToBytes(keywords.toArray(new String[0])));
|
||||
Logger.printDebug(() -> "Search using: (" + search.getEstimatedMemorySize() + " KB) keywords: " + keywords);
|
||||
}
|
||||
|
||||
bufferSearch = search;
|
||||
lastKeywordPhrasesParsed = rawKeywords; // Must set last.
|
||||
}
|
||||
|
||||
public KeywordContentFilter() {
|
||||
// Keywords are parsed on first call to isFiltered()
|
||||
addPathCallbacks(startsWithFilter, containsFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isFiltered(@Nullable String identifier, String path, byte[] protobufBufferArray,
|
||||
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
|
||||
if (contentIndex != 0 && matchedGroup == startsWithFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (!bufferSearch.matches(protobufBufferArray)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
@ -29,6 +34,10 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
private final StringFilterGroup expandableMetadata;
|
||||
private final ByteArrayFilterGroup searchResultRecommendations;
|
||||
private final StringFilterGroup searchResultVideo;
|
||||
private final StringFilterGroup compactChannelBarInner;
|
||||
private final StringFilterGroup compactChannelBarInnerButton;
|
||||
private final ByteArrayFilterGroup joinMembershipButton;
|
||||
private final StringFilterGroup horizontalShelves;
|
||||
|
||||
static {
|
||||
mixPlaylistsExceptions.addPatterns(
|
||||
|
@ -69,7 +78,8 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
|
||||
final var communityPosts = new StringFilterGroup(
|
||||
Settings.HIDE_COMMUNITY_POSTS,
|
||||
"post_base_wrapper"
|
||||
"post_base_wrapper",
|
||||
"image_post_root.eml"
|
||||
);
|
||||
|
||||
final var communityGuidelines = new StringFilterGroup(
|
||||
|
@ -103,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"
|
||||
);
|
||||
|
||||
|
@ -167,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"
|
||||
|
@ -194,9 +209,19 @@ public final class LayoutComponentsFilter extends Filter {
|
|||
"set_reminder_button"
|
||||
);
|
||||
|
||||
final var joinMembership = new StringFilterGroup(
|
||||
compactChannelBarInner = new StringFilterGroup(
|
||||
Settings.HIDE_JOIN_MEMBERSHIP_BUTTON,
|
||||
"compact_sponsor_button"
|
||||
"compact_channel_bar_inner"
|
||||
);
|
||||
|
||||
compactChannelBarInnerButton = new StringFilterGroup(
|
||||
null,
|
||||
"|button.eml|"
|
||||
);
|
||||
|
||||
joinMembershipButton = new ByteArrayFilterGroup(
|
||||
null,
|
||||
"sponsorships"
|
||||
);
|
||||
|
||||
final var channelWatermark = new StringFilterGroup(
|
||||
|
@ -219,21 +244,29 @@ 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,
|
||||
joinMembership,
|
||||
compactChannelBarInner,
|
||||
medicalPanel,
|
||||
videoQualityMenuFooter,
|
||||
infoPanel,
|
||||
|
@ -245,29 +278,53 @@ 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.
|
||||
|
||||
if (matchedGroup == compactChannelBarInner) {
|
||||
if (compactChannelBarInnerButton.check(path).isFiltered()) {
|
||||
// The filter may be broad, but in the context of a compactChannelBarInnerButton,
|
||||
// it's safe to assume that the button is the only thing that should be hidden.
|
||||
if (joinMembershipButton.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -296,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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
@ -15,7 +14,6 @@ import java.util.Spliterator;
|
|||
import java.util.function.Consumer;
|
||||
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||
import app.revanced.integrations.youtube.ByteTrieSearch;
|
||||
|
@ -124,7 +122,7 @@ class StringFilterGroup extends FilterGroup<String> {
|
|||
if (isEnabled()) {
|
||||
for (String pattern : filters) {
|
||||
if (!string.isEmpty()) {
|
||||
final int indexOf = pattern.indexOf(string);
|
||||
final int indexOf = string.indexOf(pattern);
|
||||
if (indexOf >= 0) {
|
||||
matchedIndex = indexOf;
|
||||
matchedLength = pattern.length();
|
||||
|
@ -190,9 +188,8 @@ class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
|||
/**
|
||||
* Converts the Strings into byte arrays. Used to search for text in binary data.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public ByteArrayFilterGroup(BooleanSetting setting, String... filters) {
|
||||
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
|
||||
super(setting, ByteTrieSearch.convertStringsToBytes(filters));
|
||||
}
|
||||
|
||||
private synchronized void buildFailurePatterns() {
|
||||
|
@ -386,7 +383,6 @@ abstract class Filter {
|
|||
*/
|
||||
final class DummyFilter extends Filter { }
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
@SuppressWarnings("unused")
|
||||
public final class LithoFilterPatch {
|
||||
/**
|
||||
|
|
|
@ -57,6 +57,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 +79,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,70 +1,114 @@
|
|||
package app.revanced.integrations.youtube.patches.components;
|
||||
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
||||
import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
||||
|
||||
import static app.revanced.integrations.shared.Utils.hideViewBy1dpUnderCondition;
|
||||
import static app.revanced.integrations.shared.Utils.hideViewUnderCondition;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
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 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 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 videoActionButton;
|
||||
private final StringFilterGroup suggestedAction;
|
||||
private final ByteArrayFilterGroupList suggestedActionsGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
private final StringFilterGroup actionBar;
|
||||
private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList();
|
||||
|
||||
public ShortsFilter() {
|
||||
var shorts = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS,
|
||||
//
|
||||
// Identifier components.
|
||||
//
|
||||
|
||||
var shortsIdentifiers = new StringFilterGroup(
|
||||
null, // Setting is based on navigation state.
|
||||
"shorts_shelf",
|
||||
"inline_shorts",
|
||||
"shorts_grid",
|
||||
"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(
|
||||
Settings.HIDE_SHORTS,
|
||||
null,
|
||||
"shelf_header.eml"
|
||||
);
|
||||
|
||||
// Home / subscription feed components.
|
||||
var thanksButton = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_THANKS_BUTTON,
|
||||
"suggested_action"
|
||||
);
|
||||
addIdentifierCallbacks(shortsIdentifiers, shelfHeader);
|
||||
|
||||
addIdentifierCallbacks(shorts, shelfHeader, thanksButton);
|
||||
//
|
||||
// Path components.
|
||||
//
|
||||
|
||||
// Shorts that appear in the feed/search when the device is using tablet layout.
|
||||
shortsCompactFeedVideoPath = new StringFilterGroup(Settings.HIDE_SHORTS,
|
||||
"compact_video.eml");
|
||||
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(Settings.HIDE_SHORTS,
|
||||
"/frame0.jpg");
|
||||
shortsCompactFeedVideoBuffer = new ByteArrayFilterGroup(null, "/frame0.jpg");
|
||||
|
||||
// Shorts player components.
|
||||
var joinButton = new StringFilterGroup(
|
||||
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"
|
||||
);
|
||||
|
@ -74,102 +118,194 @@ public final class ShortsFilter extends Filter {
|
|||
"subscribe_button"
|
||||
);
|
||||
|
||||
subscribeButtonPaused = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED,
|
||||
"shorts_paused_state"
|
||||
paidPromotionButton = new StringFilterGroup(
|
||||
Settings.HIDE_PAID_PROMOTION_LABEL,
|
||||
"reel_player_disclosure.eml"
|
||||
);
|
||||
|
||||
channelBar = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_CHANNEL_BAR,
|
||||
REEL_CHANNEL_BAR_PATH
|
||||
);
|
||||
|
||||
soundButton = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_SOUND_BUTTON,
|
||||
"reel_pivot_button"
|
||||
);
|
||||
|
||||
infoPanel = new StringFilterGroup(
|
||||
Settings.HIDE_SHORTS_INFO_PANEL,
|
||||
"shorts_info_panel_overview"
|
||||
);
|
||||
|
||||
videoActionButton = new StringFilterGroup(
|
||||
actionBar = new StringFilterGroup(
|
||||
null,
|
||||
"ContainerType|shorts_video_action_button"
|
||||
"shorts_action_bar"
|
||||
);
|
||||
|
||||
suggestedAction = new StringFilterGroup(
|
||||
null,
|
||||
"suggested_action.eml"
|
||||
);
|
||||
|
||||
addPathCallbacks(
|
||||
shortsCompactFeedVideoPath,
|
||||
joinButton, subscribeButton, subscribeButtonPaused,
|
||||
channelBar, soundButton, infoPanel, videoActionButton
|
||||
shortsCompactFeedVideoPath, suggestedAction, actionBar, joinButton, subscribeButton,
|
||||
paidPromotionButton, pausedOverlayButtons, channelBar, fullVideoLinkLabel, videoTitle,
|
||||
reelSoundMetadata, soundButton, infoPanel
|
||||
);
|
||||
|
||||
var shortsCommentButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_COMMENTS_BUTTON,
|
||||
"reel_comment_button"
|
||||
//
|
||||
// Action buttons
|
||||
//
|
||||
videoActionButtonGroupList.addAll(
|
||||
// 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"
|
||||
)
|
||||
);
|
||||
|
||||
var shortsShareButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_SHARE_BUTTON,
|
||||
"reel_share_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_"
|
||||
)
|
||||
);
|
||||
|
||||
var shortsRemixButton = new ByteArrayFilterGroup(
|
||||
Settings.HIDE_SHORTS_REMIX_BUTTON,
|
||||
"reel_remix_button"
|
||||
);
|
||||
|
||||
videoActionButtonGroupList.addAll(shortsCommentButton, shortsShareButton, shortsRemixButton);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 == 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 (contentIndex == 0 && shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
|
||||
if (shouldHideShortsFeedItems() && contentIndex == 0
|
||||
&& shortsCompactFeedVideoBuffer.check(protobufBufferArray).isFiltered()) {
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Video action buttons (comment, share, remix) have the same path.
|
||||
if (matchedGroup == videoActionButton) {
|
||||
if (videoActionButtonGroupList.check(protobufBufferArray).isFiltered()) return super.isFiltered(
|
||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||
);
|
||||
// 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
|
||||
);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Filter other path groups from pathFilterGroupList, only when reelChannelBar is visible
|
||||
// to avoid false positives.
|
||||
if (matchedGroup == subscribeButton) {
|
||||
if (path.startsWith(REEL_CHANNEL_BAR_PATH)) return super.isFiltered(
|
||||
identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex
|
||||
);
|
||||
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 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.
|
||||
if (contentIndex != 0) return false;
|
||||
} else {
|
||||
// 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.
|
||||
if (contentIndex != 0) return false;
|
||||
}
|
||||
|
||||
if (!shouldHideShortsFeedItems()) return false;
|
||||
}
|
||||
|
||||
// Super class handles logging.
|
||||
return super.isFiltered(identifier, path, protobufBufferArray, matchedGroup, contentType, contentIndex);
|
||||
}
|
||||
|
||||
private static boolean shouldHideShortsFeedItems() {
|
||||
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;
|
||||
}
|
||||
|
||||
public static void hideShortsShelf(final View shortsShelfView) {
|
||||
hideViewBy1dpUnderCondition(Settings.HIDE_SHORTS, shortsShelfView);
|
||||
if (shouldHideShortsFeedItems()) {
|
||||
Utils.hideViewByLayoutParams(shortsShelfView);
|
||||
}
|
||||
}
|
||||
|
||||
// region Hide the buttons in older versions of YouTube. New versions use Litho.
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
package app.revanced.integrations.youtube.patches.spoof;
|
||||
|
||||
import static app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
|
||||
import static app.revanced.integrations.shared.Utils.containsAny;
|
||||
|
||||
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.patches.VideoInformation;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import app.revanced.integrations.youtube.patches.VideoInformation;
|
||||
import app.revanced.integrations.youtube.settings.Settings;
|
||||
import app.revanced.integrations.youtube.shared.PlayerType;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
import static app.revanced.integrations.shared.Utils.containsAny;
|
||||
import static app.revanced.integrations.youtube.patches.spoof.requests.StoryboardRendererRequester.getStoryboardRenderer;
|
||||
|
||||
/** @noinspection unused*/
|
||||
@Deprecated
|
||||
public class SpoofSignaturePatch {
|
||||
/**
|
||||
* Parameter (also used by
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.annotation.Nullable;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Deprecated
|
||||
public final class StoryboardRenderer {
|
||||
@Nullable
|
||||
private final String spec;
|
||||
|
|
|
@ -10,6 +10,7 @@ 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(
|
||||
|
@ -36,7 +37,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);
|
||||
|
@ -84,7 +85,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,6 +19,7 @@ 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 {
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -70,14 +70,16 @@ public class LicenseActivityHook {
|
|||
|
||||
private static void setToolbarTitle(Activity activity, String toolbarTitleResourceName) {
|
||||
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
||||
TextView toolbarTextView = Objects.requireNonNull(getChildView(toolbar, view -> view instanceof TextView));
|
||||
TextView toolbarTextView = Objects.requireNonNull(getChildView(toolbar, false,
|
||||
view -> view instanceof TextView));
|
||||
toolbarTextView.setText(getResourceIdentifier(toolbarTitleResourceName, "string"));
|
||||
}
|
||||
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
private static void setBackButton(Activity activity) {
|
||||
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
||||
ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, view -> view instanceof ImageButton));
|
||||
ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, false,
|
||||
view -> view instanceof ImageButton));
|
||||
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
|
||||
? "yt_outline_arrow_left_white_24"
|
||||
: "yt_outline_arrow_left_black_24",
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -45,31 +49,35 @@ 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_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 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, 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));
|
||||
"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());
|
||||
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 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_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);
|
||||
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);
|
||||
|
@ -79,7 +87,6 @@ public class Settings extends BaseSettings {
|
|||
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_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, 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);
|
||||
public static final BooleanSetting HIDE_EMERGENCY_BOX = new BooleanSetting("revanced_hide_emergency_box", TRUE);
|
||||
|
@ -94,23 +101,27 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_GRAY_SEPARATOR = new BooleanSetting("revanced_hide_gray_separator", TRUE);
|
||||
public static final BooleanSetting HIDE_HIDE_CHANNEL_GUIDELINES = new BooleanSetting("revanced_hide_channel_guidelines", TRUE);
|
||||
public static final BooleanSetting HIDE_HIDE_INFO_PANELS = new BooleanSetting("revanced_hide_info_panels", TRUE);
|
||||
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true);
|
||||
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_LOAD_MORE_BUTTON = new BooleanSetting("revanced_hide_load_more_button", TRUE, 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));
|
||||
@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);
|
||||
public static final BooleanSetting HIDE_SHORTS_BUTTON = new BooleanSetting("revanced_hide_shorts_button", TRUE, true);
|
||||
public static final BooleanSetting HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES = new BooleanSetting("revanced_hide_subscribers_community_guidelines", TRUE);
|
||||
public static final BooleanSetting HIDE_SUBSCRIPTIONS_BUTTON = new BooleanSetting("revanced_hide_subscriptions_button", FALSE, true);
|
||||
public static final BooleanSetting HIDE_TIMED_REACTIONS = new BooleanSetting("revanced_hide_timed_reactions", TRUE);
|
||||
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);
|
||||
|
@ -121,12 +132,18 @@ public class Settings extends BaseSettings {
|
|||
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 SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON = new BooleanSetting("revanced_switch_create_with_notifications_button", TRUE, true);
|
||||
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);
|
||||
|
||||
// Description
|
||||
public static final BooleanSetting HIDE_CHAPTERS = new BooleanSetting("revanced_hide_chapters", TRUE);
|
||||
public static final BooleanSetting HIDE_INFO_CARDS_SECTION = new BooleanSetting("revanced_hide_info_cards_section", TRUE);
|
||||
|
@ -136,17 +153,29 @@ public class Settings extends BaseSettings {
|
|||
public static final BooleanSetting HIDE_TRANSCIPT_SECTION = new BooleanSetting("revanced_hide_transcript_section", TRUE);
|
||||
|
||||
// Shorts
|
||||
public static final BooleanSetting HIDE_SHORTS = new BooleanSetting("revanced_hide_shorts", FALSE, true);
|
||||
@Deprecated public static final BooleanSetting DEPRECATED_HIDE_SHORTS = new BooleanSetting("revanced_hide_shorts", 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_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);
|
||||
public static final BooleanSetting HIDE_SHORTS_REMIX_BUTTON = new BooleanSetting("revanced_hide_shorts_remix_button", TRUE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SHARE_BUTTON = new BooleanSetting("revanced_hide_shorts_share_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_INFO_PANEL = new BooleanSetting("revanced_hide_shorts_info_panel", TRUE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_sound_button", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_VIDEO_TITLE = new BooleanSetting("revanced_hide_shorts_video_title", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_SOUND_METADATA_LABEL = new BooleanSetting("revanced_hide_shorts_sound_metadata_label", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_FULL_VIDEO_LINK_LABEL = new BooleanSetting("revanced_hide_shorts_full_video_link_label", FALSE);
|
||||
public static final BooleanSetting HIDE_SHORTS_NAVIGATION_BAR = new BooleanSetting("revanced_hide_shorts_navigation_bar", TRUE, true);
|
||||
|
||||
// Seekbar
|
||||
|
@ -176,6 +205,7 @@ public class Settings extends BaseSettings {
|
|||
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);
|
||||
|
||||
|
@ -193,11 +223,11 @@ public class Settings extends BaseSettings {
|
|||
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);
|
||||
public static final StringSetting ANNOUNCEMENT_CONSUMER = new StringSetting("revanced_announcement_consumer", "", false, false);
|
||||
@Deprecated
|
||||
@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);
|
||||
|
@ -219,10 +249,14 @@ 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));
|
||||
|
||||
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));
|
||||
// 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));
|
||||
|
||||
// ReturnYoutubeDislike
|
||||
|
@ -346,6 +380,20 @@ public class Settings extends BaseSettings {
|
|||
}
|
||||
|
||||
|
||||
// Remove any previously saved announcement consumer (a random generated string).
|
||||
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);
|
||||
|
|
|
@ -0,0 +1,318 @@
|
|||
package app.revanced.integrations.youtube.shared;
|
||||
|
||||
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 {
|
||||
|
||||
//
|
||||
// Search bar
|
||||
//
|
||||
|
||||
private static volatile WeakReference<View> searchBarResultsRef = new WeakReference<>(null);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void searchBarResultsViewLoaded(View searchbarResults) {
|
||||
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() {
|
||||
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 String lastYTNavigationEnumName;
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setLastAppNavigationEnum(@Nullable Enum<?> ytNavigationEnumName) {
|
||||
if (ytNavigationEnumName != null) {
|
||||
lastYTNavigationEnumName = ytNavigationEnumName.name();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void navigationTabLoaded(final View navigationButtonGroup) {
|
||||
try {
|
||||
String lastEnumName = lastYTNavigationEnumName;
|
||||
|
||||
for (NavigationButton button : NavigationButton.values()) {
|
||||
if (button.ytEnumName.equals(lastEnumName)) {
|
||||
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()) {
|
||||
Logger.printException(() -> "Unknown tab: " + lastEnumName
|
||||
+ " view: " + navigationButtonGroup.getClass());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "navigationTabLoaded failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*
|
||||
* Unique hook just for the 'Create' and 'You' tab.
|
||||
*/
|
||||
public static void navigationImageResourceTabLoaded(View view) {
|
||||
// 'You' tab has no YT enum name and the enum hook is not called for it.
|
||||
// Compare the last enum to figure out which tab this actually is.
|
||||
if (CREATE.ytEnumName.equals(lastYTNavigationEnumName)) {
|
||||
navigationTabLoaded(view);
|
||||
} else {
|
||||
lastYTNavigationEnumName = NavigationButton.LIBRARY_YOU.ytEnumName;
|
||||
navigationTabLoaded(view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(NavigationButton button, View tabView) {
|
||||
// Code is added during patching.
|
||||
}
|
||||
|
||||
public enum NavigationButton {
|
||||
HOME("PIVOT_HOME"),
|
||||
SHORTS("TAB_SHORTS"),
|
||||
/**
|
||||
* Create new video tab.
|
||||
* 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"),
|
||||
/**
|
||||
* Notifications tab. Only present when
|
||||
* {@link Settings#SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON} is active.
|
||||
*/
|
||||
NOTIFICATIONS("TAB_ACTIVITY"),
|
||||
/**
|
||||
* Library tab when the user is not logged in.
|
||||
*/
|
||||
LIBRARY_LOGGED_OUT("ACCOUNT_CIRCLE"),
|
||||
/**
|
||||
* User is logged in with incognito mode enabled.
|
||||
*/
|
||||
LIBRARY_INCOGNITO("INCOGNITO_CIRCLE"),
|
||||
/**
|
||||
* Old library tab (pre 'You' layout), only present when version spoofing.
|
||||
*/
|
||||
LIBRARY_OLD_UI("VIDEO_LIBRARY_WHITE"),
|
||||
/**
|
||||
* 'You' library tab that is sometimes momentarily loaded.
|
||||
* When this is loaded, {@link #LIBRARY_YOU} is also present.
|
||||
*
|
||||
* This might be a temporary tab while the user profile photo is loading,
|
||||
* but its exact purpose is not entirely clear.
|
||||
*/
|
||||
LIBRARY_PIVOT_UNKNOWN("PIVOT_LIBRARY"),
|
||||
/**
|
||||
* Modern library tab with 'You' layout.
|
||||
*/
|
||||
// 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 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() {
|
||||
waitForNavButtonLatchIfNeeded();
|
||||
return selectedNavigationButton;
|
||||
}
|
||||
|
||||
/**
|
||||
* YouTube enum name for this tab.
|
||||
*/
|
||||
private final String ytEnumName;
|
||||
|
||||
NavigationButton(String ytEnumName) {
|
||||
this.ytEnumName = ytEnumName;
|
||||
}
|
||||
|
||||
public boolean isLibraryOrYouTab() {
|
||||
return this == LIBRARY_YOU || this == LIBRARY_PIVOT_UNKNOWN
|
||||
|| this == LIBRARY_OLD_UI || this == LIBRARY_INCOGNITO
|
||||
|| this == LIBRARY_LOGGED_OUT;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -132,4 +132,8 @@ enum class PlayerType {
|
|||
fun isNoneHiddenOrMinimized(): Boolean {
|
||||
return isNoneHiddenOrSlidingMinimized() || this == WATCH_WHILE_MINIMIZED
|
||||
}
|
||||
|
||||
fun isMaximizedOrFullscreen(): Boolean {
|
||||
return this == WATCH_WHILE_MAXIMIZED || this == WATCH_WHILE_FULLSCREEN
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.0-dev.8
|
||||
version = 1.8.0
|
||||
|
|
Loading…
Reference in New Issue