mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-05-09 10:54:26 +02:00
chore: merge branch dev
to main
(#385)
This commit is contained in:
commit
7dc71e6861
196
CHANGELOG.md
196
CHANGELOG.md
@ -1,3 +1,199 @@
|
|||||||
|
# [0.108.0-dev.24](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.23...v0.108.0-dev.24) (2023-05-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/return-youtube-dislike:** fix dislikes not showing for video opened from feed autoplay ([#408](https://github.com/revanced/revanced-integrations/issues/408)) ([307315c](https://github.com/revanced/revanced-integrations/commit/307315c43c68a47c983384351a617f5c5f508b4f))
|
||||||
|
|
||||||
|
# [0.108.0-dev.23](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.22...v0.108.0-dev.23) (2023-05-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **reddit:** add `sanitize-sharing-links` patch ([#407](https://github.com/revanced/revanced-integrations/issues/407)) ([191cc71](https://github.com/revanced/revanced-integrations/commit/191cc711de1ecbf6632fc27d32ee4f0c81413c57))
|
||||||
|
|
||||||
|
# [0.108.0-dev.22](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.21...v0.108.0-dev.22) (2023-05-20)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **twitter:** correctly resolve to integrations methods ([cd93917](https://github.com/revanced/revanced-integrations/commit/cd93917148e2f7695effb15183f53b84ddb9800a))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **twitter/hide-recommended-users:** hide "Who to follow" ([c7cabc0](https://github.com/revanced/revanced-integrations/commit/c7cabc0b5799464ed75d290dfae5fcd2faa4fc94))
|
||||||
|
|
||||||
|
# [0.108.0-dev.21](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.20...v0.108.0-dev.21) (2023-05-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/settings:** fix non functional back button in settings ([#404](https://github.com/revanced/revanced-integrations/issues/404)) ([0c55d70](https://github.com/revanced/revanced-integrations/commit/0c55d70370dad9275dfb5bc3817f71d4290f5a13))
|
||||||
|
|
||||||
|
# [0.108.0-dev.20](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.19...v0.108.0-dev.20) (2023-05-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube/copy-video-url:** add tap and hold functionality to copy video url buttons ([#403](https://github.com/revanced/revanced-integrations/issues/403)) ([80689ef](https://github.com/revanced/revanced-integrations/commit/80689eff5b2deb971feb1fc59e987ef835506bae))
|
||||||
|
|
||||||
|
# [0.108.0-dev.19](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.18...v0.108.0-dev.19) (2023-05-19)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube:** support version `18.19.35` ([b47a781](https://github.com/revanced/revanced-integrations/commit/b47a781ba710e6fb66e144ef95cdd51af358e4de))
|
||||||
|
|
||||||
|
# [0.108.0-dev.18](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.17...v0.108.0-dev.18) (2023-05-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add capability to filter from protobuf buffer ([5652c32](https://github.com/revanced/revanced-integrations/commit/5652c323455b58f6760d4938c79d704c22fd546c))
|
||||||
|
* **youtube/hide-shorts-components:** hide navigation bar ([ac13d10](https://github.com/revanced/revanced-integrations/commit/ac13d1030561905a81059ad0db31a749833a31cd))
|
||||||
|
* **youtube:** add `hide-shorts-components` patch ([5ec90db](https://github.com/revanced/revanced-integrations/commit/5ec90db28a46e8f5d79f4793c141a7411a2da05d))
|
||||||
|
|
||||||
|
# [0.108.0-dev.17](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.16...v0.108.0-dev.17) (2023-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/sponsorblock:** fix toast shown when scrubbing thru a paused video ([#401](https://github.com/revanced/revanced-integrations/issues/401)) ([7da5673](https://github.com/revanced/revanced-integrations/commit/7da56738a14a36fbf66f05d28fd886baaafbee3f))
|
||||||
|
|
||||||
|
# [0.108.0-dev.16](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.15...v0.108.0-dev.16) (2023-05-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube:** add options to disable toasts on connection errors ([#402](https://github.com/revanced/revanced-integrations/issues/402)) ([ae18edd](https://github.com/revanced/revanced-integrations/commit/ae18edd047d7979307bc28f28db17bae2c5cc226))
|
||||||
|
|
||||||
|
# [0.108.0-dev.15](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.14...v0.108.0-dev.15) (2023-05-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube:** import / export of revanced settings ([#388](https://github.com/revanced/revanced-integrations/issues/388)) ([c3f08d8](https://github.com/revanced/revanced-integrations/commit/c3f08d8d7e8116496611b85508fbd54bb3a71992))
|
||||||
|
|
||||||
|
# [0.108.0-dev.14](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.13...v0.108.0-dev.14) (2023-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/return-youtube-dislikes:** fix temporarily frozen video after opening a shorts ([#396](https://github.com/revanced/revanced-integrations/issues/396)) ([6a94bd2](https://github.com/revanced/revanced-integrations/commit/6a94bd2237be9cde6256c83fcec72b3f0de83496))
|
||||||
|
|
||||||
|
# [0.108.0-dev.13](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.12...v0.108.0-dev.13) (2023-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/remember-video-quality:** do not show 'auto' in video resolution picker if a default quality is set ([#400](https://github.com/revanced/revanced-integrations/issues/400)) ([e30d120](https://github.com/revanced/revanced-integrations/commit/e30d1201c992f4896a0b7106230377d78506cd6f))
|
||||||
|
|
||||||
|
# [0.108.0-dev.12](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.11...v0.108.0-dev.12) (2023-05-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/swipe-controls:** restart when "press to swipe" preference is changed ([#399](https://github.com/revanced/revanced-integrations/issues/399)) ([a3d754c](https://github.com/revanced/revanced-integrations/commit/a3d754c209e443135759850c7634708b23330a7c))
|
||||||
|
|
||||||
|
# [0.108.0-dev.11](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.10...v0.108.0-dev.11) (2023-05-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **twitch:** add `auto-claim-channel-points` patch ([#398](https://github.com/revanced/revanced-integrations/issues/398)) ([d7f050b](https://github.com/revanced/revanced-integrations/commit/d7f050ba2ff513c91cccbf0095fc7756dbb47400))
|
||||||
|
|
||||||
|
# [0.108.0-dev.10](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.9...v0.108.0-dev.10) (2023-05-12)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube:** add `hide-filter-bar` patch ([9649c3d](https://github.com/revanced/revanced-integrations/commit/9649c3dbc8406c3639c4fff9dd179d6d29886e60))
|
||||||
|
|
||||||
|
# [0.108.0-dev.9](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.8...v0.108.0-dev.9) (2023-05-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube/video-speed:** change custom video speeds inside app settings ([#393](https://github.com/revanced/revanced-integrations/issues/393)) ([b42790f](https://github.com/revanced/revanced-integrations/commit/b42790fbca0f6c854d41871834fd6266dd2ea106))
|
||||||
|
|
||||||
|
# [0.108.0-dev.8](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.7...v0.108.0-dev.8) (2023-05-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/theme:** apply custom seekbar color to video thumbnails ([#391](https://github.com/revanced/revanced-integrations/issues/391)) ([ae99408](https://github.com/revanced/revanced-integrations/commit/ae994086360b45340ed1ed896c35917d785bb4f9))
|
||||||
|
|
||||||
|
# [0.108.0-dev.7](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.6...v0.108.0-dev.7) (2023-05-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/hide-ads:** don't filter for `reels_player_overlay` ([415c194](https://github.com/revanced/revanced-integrations/commit/415c1948fccdc8eb27b76b043996017c5c56eac3))
|
||||||
|
|
||||||
|
# [0.108.0-dev.6](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.5...v0.108.0-dev.6) (2023-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/spoof-app-version:** restore watch history preview ([#394](https://github.com/revanced/revanced-integrations/issues/394)) ([4c7f737](https://github.com/revanced/revanced-integrations/commit/4c7f737913a0c3690f8230c51f6dd217e8b04c7a))
|
||||||
|
|
||||||
|
# [0.108.0-dev.5](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.4...v0.108.0-dev.5) (2023-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/remember-video-quality:** fix default video quality/speed being applied when resuming app ([#392](https://github.com/revanced/revanced-integrations/issues/392)) ([c97d1b7](https://github.com/revanced/revanced-integrations/commit/c97d1b7ee5be6a0f097f2995321608bc74f5822c))
|
||||||
|
|
||||||
|
# [0.108.0-dev.4](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.3...v0.108.0-dev.4) (2023-05-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube/hide-player-overlay:** make it toggleable in settings ([#382](https://github.com/revanced/revanced-integrations/issues/382)) ([1b4aa0f](https://github.com/revanced/revanced-integrations/commit/1b4aa0fcc6b89acd4156e93685b1da7519aa7148))
|
||||||
|
|
||||||
|
# [0.108.0-dev.3](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.2...v0.108.0-dev.3) (2023-05-07)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube:** `hide-load-more-button` patch ([#389](https://github.com/revanced/revanced-integrations/issues/389)) ([7da9d44](https://github.com/revanced/revanced-integrations/commit/7da9d440eedfc895b49aac40498f0279156ad117))
|
||||||
|
|
||||||
|
# [0.108.0-dev.2](https://github.com/revanced/revanced-integrations/compare/v0.108.0-dev.1...v0.108.0-dev.2) (2023-05-05)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/theme:** fix app crash if user clears seekbar color ([#390](https://github.com/revanced/revanced-integrations/issues/390)) ([e2f5290](https://github.com/revanced/revanced-integrations/commit/e2f52905dc445f881666c06877c3a69306335dcb))
|
||||||
|
|
||||||
|
# [0.108.0-dev.1](https://github.com/revanced/revanced-integrations/compare/v0.107.1-dev.3...v0.108.0-dev.1) (2023-05-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **youtube/settings:** add reset button to edit preference dialog ([#383](https://github.com/revanced/revanced-integrations/issues/383)) ([cb5a4d0](https://github.com/revanced/revanced-integrations/commit/cb5a4d0c9b3b340928695fcb1d10b164a6dcef27))
|
||||||
|
|
||||||
|
## [0.107.1-dev.3](https://github.com/revanced/revanced-integrations/compare/v0.107.1-dev.2...v0.107.1-dev.3) (2023-05-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/theme:** fix toast shown on fresh app install ([#381](https://github.com/revanced/revanced-integrations/issues/381)) ([2dc431f](https://github.com/revanced/revanced-integrations/commit/2dc431f1bf54c12dfc45c4511a0b0792e214be4f))
|
||||||
|
|
||||||
|
## [0.107.1-dev.2](https://github.com/revanced/revanced-integrations/compare/v0.107.1-dev.1...v0.107.1-dev.2) (2023-05-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/sponsorblock:** fix skip button in wrong location when full screen and comments visible ([#387](https://github.com/revanced/revanced-integrations/issues/387)) ([486b79b](https://github.com/revanced/revanced-integrations/commit/486b79b4e4927d4c05cfb4d5222a1d74fe60e327))
|
||||||
|
|
||||||
|
## [0.107.1-dev.1](https://github.com/revanced/revanced-integrations/compare/v0.107.0...v0.107.1-dev.1) (2023-05-02)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **youtube/return-youtube-dislike:** fix potential error toast when using old UI layout ([#384](https://github.com/revanced/revanced-integrations/issues/384)) ([6c36bee](https://github.com/revanced/revanced-integrations/commit/6c36beeda139156bfbb5a17bc89aa63c25afa83c))
|
||||||
|
|
||||||
# [0.107.0](https://github.com/revanced/revanced-integrations/compare/v0.106.0...v0.107.0) (2023-05-02)
|
# [0.107.0](https://github.com/revanced/revanced-integrations/compare/v0.106.0...v0.107.0) (2023-05-02)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
# ReVanced Integrations
|
# 🔩 ReVanced Integrations
|
||||||
|
|
||||||
|
The official ReVanced Integrations containing classes to be merged by ReVanced Patcher.
|
||||||
|
|
||||||
|
## ❓ How to use debugging:
|
||||||
|
|
||||||
# How to use debugging:
|
|
||||||
- Usage on Windows: ```adb logcat | findstr "revanced" > log.txt```
|
- Usage on Windows: ```adb logcat | findstr "revanced" > log.txt```
|
||||||
- Usage on Linux: ```adb logcat | grep --line-buffered "revanced" > log.txt```
|
- Usage on Linux: ```adb logcat | grep --line-buffered "revanced" > log.txt```
|
||||||
|
|
||||||
This will write the log to a file called log.txt which you can view then.
|
This will write the log to a file called log.txt which you can view then.
|
||||||
|
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
package app.revanced.integrations.adremover;
|
|
||||||
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.FrameLayout;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
import android.widget.Toolbar;
|
|
||||||
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
|
|
||||||
public class AdRemoverAPI {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes Reels and Home ads
|
|
||||||
*
|
|
||||||
* @param view
|
|
||||||
*/
|
|
||||||
//ToDo: refactor this
|
|
||||||
public static void HideViewWithLayout1dp(View view) {
|
|
||||||
if (view instanceof LinearLayout) {
|
|
||||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(1, 1);
|
|
||||||
view.setLayoutParams(layoutParams);
|
|
||||||
} else if (view instanceof FrameLayout) {
|
|
||||||
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(1, 1);
|
|
||||||
view.setLayoutParams(layoutParams2);
|
|
||||||
} else if (view instanceof RelativeLayout) {
|
|
||||||
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(1, 1);
|
|
||||||
view.setLayoutParams(layoutParams3);
|
|
||||||
} else if (view instanceof Toolbar) {
|
|
||||||
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(1, 1);
|
|
||||||
view.setLayoutParams(layoutParams4);
|
|
||||||
} else if (view instanceof ViewGroup) {
|
|
||||||
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(1, 1);
|
|
||||||
view.setLayoutParams(layoutParams5);
|
|
||||||
} else {
|
|
||||||
LogHelper.printDebug(() -> "HideViewWithLayout1dp - Id: " + view.getId() + " Type: " + view.getClass().getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -5,6 +5,6 @@ import app.revanced.integrations.settings.SettingsEnum;
|
|||||||
public class AutoRepeatPatch {
|
public class AutoRepeatPatch {
|
||||||
//Used by app.revanced.patches.youtube.layout.autorepeat.patch.AutoRepeatPatch
|
//Used by app.revanced.patches.youtube.layout.autorepeat.patch.AutoRepeatPatch
|
||||||
public static boolean shouldAutoRepeat() {
|
public static boolean shouldAutoRepeat() {
|
||||||
return SettingsEnum.PREFERRED_AUTO_REPEAT.getBoolean();
|
return SettingsEnum.AUTO_REPEAT.getBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
final class ButtonsPatch extends Filter {
|
|
||||||
private final BlockRule actionBarRule;
|
|
||||||
|
|
||||||
public ButtonsPatch() {
|
|
||||||
actionBarRule = new BlockRule(null, "video_action_bar");
|
|
||||||
pathRegister.registerAll(
|
|
||||||
new BlockRule(SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON, "|like_button", "dislike_button"),
|
|
||||||
new BlockRule(SettingsEnum.HIDE_DOWNLOAD_BUTTON, "download_button"),
|
|
||||||
new BlockRule(SettingsEnum.HIDE_PLAYLIST_BUTTON, "save_to_playlist_button"),
|
|
||||||
new BlockRule(SettingsEnum.HIDE_CLIP_BUTTON, "|clip_button.eml|"),
|
|
||||||
new BlockRule(SettingsEnum.HIDE_ACTION_BUTTONS, "ContainerType|video_action_button", "|CellType|CollectionType|CellType|ContainerType|button.eml|")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean canHideActionBar() {
|
|
||||||
for (BlockRule rule : pathRegister) if (!rule.isEnabled()) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean filter(final String path, final String identifier) {
|
|
||||||
// If everything is hidden, then also hide the video bar itself.
|
|
||||||
if (canHideActionBar() && actionBarRule.check(identifier).isBlocked()) return true;
|
|
||||||
|
|
||||||
return pathRegister.contains(path);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
|
|
||||||
final class CommentsPatch extends Filter {
|
|
||||||
|
|
||||||
public CommentsPatch() {
|
|
||||||
var comments = new BlockRule(SettingsEnum.HIDE_COMMENTS_SECTION, "video_metadata_carousel", "_comments");
|
|
||||||
var previewComment = new BlockRule(
|
|
||||||
SettingsEnum.HIDE_PREVIEW_COMMENT,
|
|
||||||
"|carousel_item",
|
|
||||||
"comments_entry_point_teaser",
|
|
||||||
"comments_entry_point_simplebox"
|
|
||||||
);
|
|
||||||
|
|
||||||
this.pathRegister.registerAll(
|
|
||||||
comments,
|
|
||||||
previewComment
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean filter(String path, String _identifier) {
|
|
||||||
if (!pathRegister.contains(path)) return false;
|
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Blocked: " + path);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,22 +2,46 @@ package app.revanced.integrations.patches;
|
|||||||
|
|
||||||
import static app.revanced.integrations.utils.StringRef.str;
|
import static app.revanced.integrations.utils.StringRef.str;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public class CopyVideoUrlPatch {
|
public class CopyVideoUrlPatch {
|
||||||
public static void copyUrl(Boolean withTimestamp) {
|
|
||||||
|
public static void copyUrl(boolean withTimestamp) {
|
||||||
try {
|
try {
|
||||||
String url = String.format("https://youtu.be/%s", VideoInformation.getVideoId());
|
StringBuilder builder = new StringBuilder("https://youtu.be/");
|
||||||
if (withTimestamp) {
|
builder.append(VideoInformation.getVideoId());
|
||||||
long seconds = VideoInformation.getVideoTime() / 1000;
|
final long currentVideoTimeInSeconds = VideoInformation.getVideoTime() / 1000;
|
||||||
url += String.format("?t=%s", seconds);
|
if (withTimestamp && currentVideoTimeInSeconds > 0) {
|
||||||
|
final long hour = currentVideoTimeInSeconds / (60 * 60);
|
||||||
|
final long minute = (currentVideoTimeInSeconds / 60) % 60;
|
||||||
|
final long second = currentVideoTimeInSeconds % 60;
|
||||||
|
builder.append("?t=");
|
||||||
|
if (hour > 0) {
|
||||||
|
builder.append(hour).append("h");
|
||||||
|
}
|
||||||
|
if (minute > 0) {
|
||||||
|
builder.append(minute).append("m");
|
||||||
|
}
|
||||||
|
if (second > 0) {
|
||||||
|
builder.append(second).append("s");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReVancedUtils.setClipboard(url);
|
ReVancedUtils.setClipboard(builder.toString());
|
||||||
ReVancedUtils.showToastShort(str("share_copy_url_success"));
|
// Do not show a toast if using Android 13+ as it shows it's own toast.
|
||||||
|
// But if the user copied with a timestamp then show a toast.
|
||||||
|
// Unfortunately this will show 2 toasts on Android 13+, but no way around this.
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2 || (withTimestamp && currentVideoTimeInSeconds > 0)) {
|
||||||
|
ReVancedUtils.showToastShort(withTimestamp && currentVideoTimeInSeconds > 0
|
||||||
|
? str("revanced_share_copy_url_timestamp_success")
|
||||||
|
: str("revanced_share_copy_url_success"));
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogHelper.printException(() -> "Failed to generate video url", e);
|
LogHelper.printException(() -> "Failed to generate video url", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,13 @@ import app.revanced.integrations.settings.SettingsEnum;
|
|||||||
|
|
||||||
public class DisableAutoCaptionsPatch {
|
public class DisableAutoCaptionsPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by injected code. Do not delete.
|
||||||
|
*/
|
||||||
public static boolean captionsButtonDisabled;
|
public static boolean captionsButtonDisabled;
|
||||||
|
|
||||||
public static boolean autoCaptionsEnabled() {
|
public static boolean autoCaptionsEnabled() {
|
||||||
return SettingsEnum.CAPTIONS_ENABLED.getBoolean();
|
return SettingsEnum.AUTO_CAPTIONS.getBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,6 @@ import app.revanced.integrations.settings.SettingsEnum;
|
|||||||
public class DisableStartupShortsPlayerPatch {
|
public class DisableStartupShortsPlayerPatch {
|
||||||
//Used by app.revanced.patches.youtube.layout.startupshortsreset.patch.DisableShortsOnStartupPatch
|
//Used by app.revanced.patches.youtube.layout.startupshortsreset.patch.DisableShortsOnStartupPatch
|
||||||
public static boolean disableStartupShortsPlayer() {
|
public static boolean disableStartupShortsPlayer() {
|
||||||
return SettingsEnum.DISABLE_STARTUP_SHORTS_PLAYER.getBoolean();
|
return SettingsEnum.DISABLE_RESUMING_SHORTS_PLAYER.getBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,191 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
import app.revanced.integrations.adremover.AdRemoverAPI;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
public final class GeneralAdsPatch extends Filter {
|
|
||||||
private final String[] IGNORE = {
|
|
||||||
"home_video_with_context",
|
|
||||||
"related_video_with_context",
|
|
||||||
"comment_thread", // skip blocking anything in the comments
|
|
||||||
"|comment.", // skip blocking anything in the comments replies
|
|
||||||
"library_recent_shelf",
|
|
||||||
};
|
|
||||||
|
|
||||||
private final BlockRule custom = new CustomBlockRule(
|
|
||||||
SettingsEnum.ADREMOVER_CUSTOM_ENABLED,
|
|
||||||
SettingsEnum.ADREMOVER_CUSTOM_REMOVAL
|
|
||||||
);
|
|
||||||
|
|
||||||
public GeneralAdsPatch() {
|
|
||||||
var communityPosts = new BlockRule(SettingsEnum.ADREMOVER_COMMUNITY_POSTS_REMOVAL, "post_base_wrapper");
|
|
||||||
var communityGuidelines = new BlockRule(SettingsEnum.ADREMOVER_COMMUNITY_GUIDELINES_REMOVAL, "community_guidelines");
|
|
||||||
var subscribersCommunityGuidelines = new BlockRule(SettingsEnum.ADREMOVER_SUBSCRIBERS_COMMUNITY_GUIDELINES_REMOVAL, "sponsorships_comments_upsell");
|
|
||||||
var channelMemberShelf = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_MEMBER_SHELF_REMOVAL, "member_recognition_shelf");
|
|
||||||
var compactBanner = new BlockRule(SettingsEnum.ADREMOVER_COMPACT_BANNER_REMOVAL, "compact_banner");
|
|
||||||
var inFeedSurvey = new BlockRule(SettingsEnum.ADREMOVER_FEED_SURVEY_REMOVAL, "in_feed_survey", "slimline_survey");
|
|
||||||
var medicalPanel = new BlockRule(SettingsEnum.ADREMOVER_MEDICAL_PANEL_REMOVAL, "medical_panel");
|
|
||||||
var paidContent = new BlockRule(SettingsEnum.ADREMOVER_PAID_CONTENT_REMOVAL, "paid_content_overlay");
|
|
||||||
var merchandise = new BlockRule(SettingsEnum.ADREMOVER_MERCHANDISE_REMOVAL, "product_carousel");
|
|
||||||
var infoPanel = new BlockRule(SettingsEnum.ADREMOVER_INFO_PANEL_REMOVAL, "publisher_transparency_panel", "single_item_information_panel");
|
|
||||||
var latestPosts = new BlockRule(SettingsEnum.ADREMOVER_HIDE_LATEST_POSTS, "post_shelf");
|
|
||||||
var channelGuidelines = new BlockRule(SettingsEnum.ADREMOVER_HIDE_CHANNEL_GUIDELINES, "channel_guidelines_entry_banner");
|
|
||||||
var audioTrackButton = new BlockRule(SettingsEnum.HIDE_AUDIO_TRACK_BUTTON, "multi_feed_icon_button");
|
|
||||||
var artistCard = new BlockRule(SettingsEnum.HIDE_ARTIST_CARDS, "official_card");
|
|
||||||
var selfSponsor = new BlockRule(SettingsEnum.ADREMOVER_SELF_SPONSOR_REMOVAL, "cta_shelf_card");
|
|
||||||
var chapterTeaser = new BlockRule(SettingsEnum.ADREMOVER_CHAPTER_TEASER_REMOVAL, "expandable_metadata", "macro_markers_carousel");
|
|
||||||
var viewProducts = new BlockRule(SettingsEnum.ADREMOVER_VIEW_PRODUCTS, "product_item", "products_in_video");
|
|
||||||
var webLinkPanel = new BlockRule(SettingsEnum.ADREMOVER_WEB_SEARCH_RESULTS, "web_link_panel");
|
|
||||||
var channelBar = new BlockRule(SettingsEnum.ADREMOVER_CHANNEL_BAR, "channel_bar");
|
|
||||||
var relatedVideos = new BlockRule(SettingsEnum.ADREMOVER_RELATED_VIDEOS, "fullscreen_related_videos");
|
|
||||||
var quickActions = new BlockRule(SettingsEnum.ADREMOVER_QUICK_ACTIONS, "quick_actions");
|
|
||||||
var imageShelf = new BlockRule(SettingsEnum.ADREMOVER_IMAGE_SHELF, "image_shelf");
|
|
||||||
var graySeparator = new BlockRule(SettingsEnum.ADREMOVER_GRAY_SEPARATOR,
|
|
||||||
"cell_divider" // layout residue (gray line above the buttoned ad),
|
|
||||||
);
|
|
||||||
var buttonedAd = new BlockRule(SettingsEnum.ADREMOVER_BUTTONED_REMOVAL,
|
|
||||||
"_buttoned_layout",
|
|
||||||
"full_width_square_image_layout",
|
|
||||||
"_ad_with",
|
|
||||||
"video_display_button_group_layout",
|
|
||||||
"landscape_image_wide_button_layout"
|
|
||||||
);
|
|
||||||
var generalAds = new BlockRule(
|
|
||||||
SettingsEnum.ADREMOVER_GENERAL_ADS_REMOVAL,
|
|
||||||
"ads_video_with_context",
|
|
||||||
"banner_text_icon",
|
|
||||||
"square_image_layout",
|
|
||||||
"watch_metadata_app_promo",
|
|
||||||
"video_display_full_layout",
|
|
||||||
"hero_promo_image",
|
|
||||||
"statement_banner",
|
|
||||||
"carousel_footered_layout",
|
|
||||||
"text_image_button_layout",
|
|
||||||
"primetime_promo",
|
|
||||||
"product_details",
|
|
||||||
"full_width_portrait_image_layout",
|
|
||||||
"brand_video_shelf"
|
|
||||||
);
|
|
||||||
var movieAds = new BlockRule(
|
|
||||||
SettingsEnum.ADREMOVER_MOVIE_REMOVAL,
|
|
||||||
"browsy_bar",
|
|
||||||
"compact_movie",
|
|
||||||
"horizontal_movie_shelf",
|
|
||||||
"movie_and_show_upsell_card",
|
|
||||||
"compact_tvfilm_item",
|
|
||||||
"offer_module_root"
|
|
||||||
);
|
|
||||||
|
|
||||||
this.pathRegister.registerAll(
|
|
||||||
generalAds,
|
|
||||||
buttonedAd,
|
|
||||||
channelBar,
|
|
||||||
communityPosts,
|
|
||||||
paidContent,
|
|
||||||
latestPosts,
|
|
||||||
movieAds,
|
|
||||||
chapterTeaser,
|
|
||||||
communityGuidelines,
|
|
||||||
quickActions,
|
|
||||||
relatedVideos,
|
|
||||||
compactBanner,
|
|
||||||
inFeedSurvey,
|
|
||||||
viewProducts,
|
|
||||||
medicalPanel,
|
|
||||||
merchandise,
|
|
||||||
infoPanel,
|
|
||||||
channelGuidelines,
|
|
||||||
audioTrackButton,
|
|
||||||
artistCard,
|
|
||||||
selfSponsor,
|
|
||||||
webLinkPanel,
|
|
||||||
imageShelf,
|
|
||||||
subscribersCommunityGuidelines,
|
|
||||||
channelMemberShelf
|
|
||||||
);
|
|
||||||
|
|
||||||
var carouselAd = new BlockRule(SettingsEnum.ADREMOVER_GENERAL_ADS_REMOVAL,
|
|
||||||
"carousel_ad"
|
|
||||||
);
|
|
||||||
var shorts = new BlockRule(SettingsEnum.ADREMOVER_SHORTS_REMOVAL,
|
|
||||||
"reels_player_overlay",
|
|
||||||
"shorts_shelf",
|
|
||||||
"inline_shorts",
|
|
||||||
"shorts_grid"
|
|
||||||
);
|
|
||||||
|
|
||||||
this.identifierRegister.registerAll(
|
|
||||||
shorts,
|
|
||||||
graySeparator,
|
|
||||||
carouselAd
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean filter(final String path, final String identifier) {
|
|
||||||
BlockResult result;
|
|
||||||
|
|
||||||
if (custom.isEnabled() && custom.check(path).isBlocked())
|
|
||||||
result = BlockResult.CUSTOM;
|
|
||||||
else if (ReVancedUtils.containsAny(path, IGNORE))
|
|
||||||
result = BlockResult.IGNORED;
|
|
||||||
else if (pathRegister.contains(path) || identifierRegister.contains(identifier))
|
|
||||||
result = BlockResult.DEFINED;
|
|
||||||
else
|
|
||||||
result = BlockResult.UNBLOCKED;
|
|
||||||
|
|
||||||
LogHelper.printDebug(() -> String.format("%s (ID: %s): %s", result.message, identifier, path));
|
|
||||||
|
|
||||||
return result.filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum BlockResult {
|
|
||||||
UNBLOCKED(false, "Unblocked"),
|
|
||||||
IGNORED(false, "Ignored"),
|
|
||||||
DEFINED(true, "Blocked"),
|
|
||||||
CUSTOM(true, "Custom");
|
|
||||||
|
|
||||||
final Boolean filter;
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
BlockResult(boolean filter, String message) {
|
|
||||||
this.filter = filter;
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide a view.
|
|
||||||
*
|
|
||||||
* @param condition The setting to check for hiding the view.
|
|
||||||
* @param view The view to hide.
|
|
||||||
*/
|
|
||||||
private static void hideView(SettingsEnum condition, View view) {
|
|
||||||
if (!condition.getBoolean()) return;
|
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
|
||||||
|
|
||||||
AdRemoverAPI.HideViewWithLayout1dp(view);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide the view, which shows ads in the homepage.
|
|
||||||
*
|
|
||||||
* @param view The view, which shows ads.
|
|
||||||
*/
|
|
||||||
public static void hideAdAttributionView(View view) {
|
|
||||||
hideView(SettingsEnum.ADREMOVER_GENERAL_ADS_REMOVAL, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide the view, which shows reels in the homepage.
|
|
||||||
*
|
|
||||||
* @param view The view, which shows reels.
|
|
||||||
*/
|
|
||||||
public static void hideReelView(View view) {
|
|
||||||
hideView(SettingsEnum.ADREMOVER_SHORTS_REMOVAL, view);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -21,7 +21,7 @@ public class HDRAutoBrightnessPatch {
|
|||||||
*/
|
*/
|
||||||
public static float getHDRBrightness(float original) {
|
public static float getHDRBrightness(float original) {
|
||||||
// do nothing if disabled
|
// do nothing if disabled
|
||||||
if (!SettingsEnum.USE_HDR_AUTO_BRIGHTNESS.getBoolean()) {
|
if (!SettingsEnum.HDR_AUTO_BRIGHTNESS.getBoolean()) {
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,13 +2,12 @@ package app.revanced.integrations.patches;
|
|||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import app.revanced.integrations.adremover.AdRemoverAPI;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public class HideAlbumCardsPatch {
|
public class HideAlbumCardsPatch {
|
||||||
//Used by app.revanced.patches.youtube.layout.hidealbumcards.patch.HideAlbumCardsPatch
|
public static void hideAlbumCard(View view) {
|
||||||
public static void hideAlbumCards(View view) {
|
|
||||||
if (!SettingsEnum.HIDE_ALBUM_CARDS.getBoolean()) return;
|
if (!SettingsEnum.HIDE_ALBUM_CARDS.getBoolean()) return;
|
||||||
AdRemoverAPI.HideViewWithLayout1dp(view);
|
ReVancedUtils.hideViewByLayoutParams(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,13 +2,28 @@ package app.revanced.integrations.patches;
|
|||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import app.revanced.integrations.adremover.AdRemoverAPI;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public class HideBreakingNewsPatch {
|
public class HideBreakingNewsPatch {
|
||||||
//Used by app.revanced.patches.youtube.layout.homepage.breakingnews.patch.BreakingNewsPatch
|
|
||||||
|
/**
|
||||||
|
* When spoofing to app versions older than 17.30.35, 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 boolean isSpoofingOldVersionWithHorizontalCardListWatchHistory() {
|
||||||
|
return SettingsEnum.SPOOF_APP_VERSION.getBoolean()
|
||||||
|
&& SettingsEnum.SPOOF_APP_VERSION_TARGET.getString().compareTo("17.30.35") < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*/
|
||||||
public static void hideBreakingNews(View view) {
|
public static void hideBreakingNews(View view) {
|
||||||
if (!SettingsEnum.HIDE_BREAKING_NEWS.getBoolean()) return;
|
if (!SettingsEnum.HIDE_BREAKING_NEWS.getBoolean()
|
||||||
AdRemoverAPI.HideViewWithLayout1dp(view);
|
|| isSpoofingOldVersionWithHorizontalCardListWatchHistory()) return;
|
||||||
|
ReVancedUtils.hideViewByLayoutParams(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,13 @@ package app.revanced.integrations.patches;
|
|||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import app.revanced.integrations.adremover.AdRemoverAPI;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public class HideCrowdfundingBoxPatch {
|
public class HideCrowdfundingBoxPatch {
|
||||||
//Used by app.revanced.patches.youtube.layout.hidecrowdfundingbox.patch.HideCrowdfundingBoxPatch
|
//Used by app.revanced.patches.youtube.layout.hidecrowdfundingbox.patch.HideCrowdfundingBoxPatch
|
||||||
public static void hideCrowdfundingBox(View view) {
|
public static void hideCrowdfundingBox(View view) {
|
||||||
if (!SettingsEnum.HIDE_CROWDFUNDING_BOX.getBoolean()) return;
|
if (!SettingsEnum.HIDE_CROWDFUNDING_BOX.getBoolean()) return;
|
||||||
AdRemoverAPI.HideViewWithLayout1dp(view);
|
ReVancedUtils.hideViewByLayoutParams(view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
public final class HideFilterBarPatch {
|
||||||
|
public static int hideInFeed(final int height) {
|
||||||
|
if (SettingsEnum.HIDE_FILTER_BAR_FEED_IN_FEED.getBoolean()) return 0;
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideInRelatedVideos(final View chipView) {
|
||||||
|
if (!SettingsEnum.HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS.getBoolean()) return;
|
||||||
|
|
||||||
|
ReVancedUtils.hideViewByLayoutParams(chipView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int hideInSearch(final int height) {
|
||||||
|
if (SettingsEnum.HIDE_FILTER_BAR_FEED_IN_SEARCH.getBoolean()) return 0;
|
||||||
|
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
public class HideLoadMoreButtonPatch {
|
||||||
|
public static void hideLoadMoreButton(View view){
|
||||||
|
if(!SettingsEnum.HIDE_LOAD_MORE_BUTTON.getBoolean()) return;
|
||||||
|
ReVancedUtils.hideViewByLayoutParams(view);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package app.revanced.integrations.patches;
|
||||||
|
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public class HidePlayerOverlayPatch {
|
||||||
|
public static void hidePlayerOverlay(ImageView view) {
|
||||||
|
if (!SettingsEnum.HIDE_PLAYER_OVERLAY.getBoolean()) return;
|
||||||
|
view.setImageResource(android.R.color.transparent);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class HideShortsCommentsButtonPatch {
|
|
||||||
//Used by app.revanced.patches.youtube.layout.comments.patch.CommentsPatch
|
|
||||||
public static void hideShortsCommentsButton(View view) {
|
|
||||||
if (!SettingsEnum.HIDE_SHORTS_COMMENTS_BUTTON.getBoolean()) return;
|
|
||||||
view.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,140 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Spliterator;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
class BlockRule {
|
|
||||||
final static class BlockResult {
|
|
||||||
private final boolean blocked;
|
|
||||||
private final SettingsEnum setting;
|
|
||||||
|
|
||||||
public BlockResult(final SettingsEnum setting, final boolean blocked) {
|
|
||||||
this.setting = setting;
|
|
||||||
this.blocked = blocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SettingsEnum getSetting() {
|
|
||||||
return setting;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBlocked() {
|
|
||||||
return blocked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final SettingsEnum setting;
|
|
||||||
private final String[] blocks;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new rule for components.
|
|
||||||
*
|
|
||||||
* @param setting The setting which controls the blocking of this component.
|
|
||||||
* @param blocks The rules to block the component on.
|
|
||||||
*/
|
|
||||||
public BlockRule(final SettingsEnum setting, final String... blocks) {
|
|
||||||
this.setting = setting;
|
|
||||||
this.blocks = blocks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isEnabled() {
|
|
||||||
return setting.getBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BlockResult check(final String string) {
|
|
||||||
return new BlockResult(setting, string != null && ReVancedUtils.containsAny(string, blocks));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class CustomBlockRule extends BlockRule {
|
|
||||||
/**
|
|
||||||
* Initialize a new rule for components.
|
|
||||||
*
|
|
||||||
* @param setting The setting which controls the blocking of the components.
|
|
||||||
* @param filter The setting which contains the list of component names.
|
|
||||||
*/
|
|
||||||
public CustomBlockRule(final SettingsEnum setting, final SettingsEnum filter) {
|
|
||||||
super(setting, filter.getString().split(","));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
abstract class Filter {
|
|
||||||
final protected LithoBlockRegister pathRegister = new LithoBlockRegister();
|
|
||||||
final protected LithoBlockRegister identifierRegister = new LithoBlockRegister();
|
|
||||||
|
|
||||||
abstract boolean filter(final String path, final String identifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
final class LithoBlockRegister implements Iterable<BlockRule> {
|
|
||||||
private final ArrayList<BlockRule> blocks = new ArrayList<>();
|
|
||||||
|
|
||||||
public void registerAll(BlockRule... blocks) {
|
|
||||||
this.blocks.addAll(Arrays.asList(blocks));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Iterator<BlockRule> iterator() {
|
|
||||||
return blocks.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
@Override
|
|
||||||
public void forEach(@NonNull Consumer<? super BlockRule> action) {
|
|
||||||
blocks.forEach(action);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Spliterator<BlockRule> spliterator() {
|
|
||||||
return blocks.spliterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean contains(String path) {
|
|
||||||
for (var rule : this) {
|
|
||||||
if (!rule.isEnabled()) continue;
|
|
||||||
|
|
||||||
var result = rule.check(path);
|
|
||||||
if (result.isBlocked()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public final class LithoFilterPatch {
|
|
||||||
private static final Filter[] filters = new Filter[]{
|
|
||||||
new GeneralAdsPatch(),
|
|
||||||
new ButtonsPatch(),
|
|
||||||
new CommentsPatch(),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static boolean filter(final StringBuilder pathBuilder, final String identifier) {
|
|
||||||
var path = pathBuilder.toString();
|
|
||||||
if (path.isEmpty()) return false;
|
|
||||||
|
|
||||||
LogHelper.printDebug(() -> String.format("Searching (ID: %s): %s", identifier, path));
|
|
||||||
|
|
||||||
for (var filter : filters) {
|
|
||||||
if (filter.filter(path, identifier)) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,7 +13,7 @@ public class OpenLinksExternallyPatch {
|
|||||||
* @return The new, default service to open links with or the original service.
|
* @return The new, default service to open links with or the original service.
|
||||||
*/
|
*/
|
||||||
public static String enableExternalBrowser(String original) {
|
public static String enableExternalBrowser(String original) {
|
||||||
if (SettingsEnum.ENABLE_EXTERNAL_BROWSER.getBoolean()) original = "";
|
if (SettingsEnum.EXTERNAL_BROWSER.getBoolean()) original = "";
|
||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import app.revanced.integrations.shared.PlayerOverlays;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class PlayerOverlaysHookPatch {
|
public class PlayerOverlaysHookPatch {
|
||||||
/**
|
/**
|
||||||
* Hook into YouTubePlayerOverlaysLayout.onFinishInflate() method
|
* Injection point.
|
||||||
*
|
*
|
||||||
* @param thisRef reference to the view
|
* @param thisRef reference to the view
|
||||||
* @smali YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava / lang / Object ;)V
|
* @smali YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava / lang / Object ;)V
|
||||||
|
@ -3,32 +3,25 @@ package app.revanced.integrations.patches;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.integrations.shared.PlayerType;
|
import app.revanced.integrations.shared.PlayerType;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.shared.VideoState;
|
||||||
|
|
||||||
/**
|
|
||||||
* Hook receiver class for 'player-type-hook' patch
|
|
||||||
*
|
|
||||||
* @usedBy app.revanced.patches.youtube.misc.playertype.patch.PlayerTypeHookPatch
|
|
||||||
* @smali Lapp/revanced/integrations/patches/PlayerTypeHookPatch;
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class PlayerTypeHookPatch {
|
public class PlayerTypeHookPatch {
|
||||||
/**
|
/**
|
||||||
* Hook into YouTubePlayerOverlaysLayout.updatePlayerLayout() method
|
* Injection point.
|
||||||
*
|
|
||||||
* @param type the new player type
|
|
||||||
* @smali YouTubePlayerOverlaysLayout_updatePlayerTypeHookEX(Ljava/lang/Object;)V
|
|
||||||
*/
|
*/
|
||||||
public static void YouTubePlayerOverlaysLayout_updatePlayerTypeHookEX(@Nullable Object type) {
|
public static void setPlayerType(@Nullable Enum<?> youTubePlayerType) {
|
||||||
if (type == null) return;
|
if (youTubePlayerType == null) return;
|
||||||
|
|
||||||
// update current player type
|
PlayerType.setFromString(youTubePlayerType.name());
|
||||||
final PlayerType newType = PlayerType.safeParseFromString(type.toString());
|
}
|
||||||
if (newType == null) {
|
|
||||||
LogHelper.printException(() -> "Unknown PlayerType encountered: " + type);
|
/**
|
||||||
} else {
|
* Injection point.
|
||||||
PlayerType.setCurrent(newType);
|
*/
|
||||||
LogHelper.printDebug(() -> "PlayerType was updated to: " + newType);
|
public static void setVideoState(@Nullable Enum<?> youTubeVideoState) {
|
||||||
}
|
if (youTubeVideoState == null) return;
|
||||||
|
|
||||||
|
VideoState.setFromString(youTubeVideoState.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,42 @@ package app.revanced.integrations.patches;
|
|||||||
|
|
||||||
import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote;
|
import static app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike.Vote;
|
||||||
|
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.os.Build;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
|
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.shared.PlayerType;
|
import app.revanced.integrations.shared.PlayerType;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all interaction of UI patch components.
|
||||||
|
*
|
||||||
|
* Does not handle creating dislike spans or anything to do with {@link ReturnYouTubeDislikeApi}.
|
||||||
|
*/
|
||||||
public class ReturnYouTubeDislikePatch {
|
public class ReturnYouTubeDislikePatch {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String currentVideoId;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resource identifier of old UI dislike button.
|
* Resource identifier of old UI dislike button.
|
||||||
*/
|
*/
|
||||||
@ -60,7 +76,7 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (oldUIReplacementSpan == null || oldUIReplacementSpan.toString().equals(s.toString())) {
|
if (oldUIReplacementSpan == null || oldUIReplacementSpan.toString().equals(s.toString())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
s.replace(0, s.length(), oldUIReplacementSpan);
|
s.replace(0, s.length(), oldUIReplacementSpan); // Causes a recursive call back into this listener
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,12 +96,15 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
*
|
*
|
||||||
* Used when spoofing the older app versions of {@link SpoofAppVersionPatch}.
|
* Used when spoofing the older app versions of {@link SpoofAppVersionPatch}.
|
||||||
*/
|
*/
|
||||||
public static void setOldUILayoutDislikes(int buttonViewResourceId, @NonNull TextView textView) {
|
public static void setOldUILayoutDislikes(int buttonViewResourceId, @Nullable TextView textView) {
|
||||||
try {
|
try {
|
||||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()
|
||||||
|| buttonViewResourceId != OLD_UI_DISLIKE_BUTTON_RESOURCE_ID) {
|
|| buttonViewResourceId != OLD_UI_DISLIKE_BUTTON_RESOURCE_ID
|
||||||
|
|| textView == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
LogHelper.printDebug(() -> "setOldUILayoutDislikes");
|
||||||
|
|
||||||
if (oldUIOriginalSpan == null) {
|
if (oldUIOriginalSpan == null) {
|
||||||
// Use value of the first instance, as it appears TextViews can be recycled
|
// Use value of the first instance, as it appears TextViews can be recycled
|
||||||
// and might contain dislikes previously added by the patch.
|
// and might contain dislikes previously added by the patch.
|
||||||
@ -96,23 +115,19 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
textView.removeTextChangedListener(oldUiTextWatcher);
|
textView.removeTextChangedListener(oldUiTextWatcher);
|
||||||
textView.addTextChangedListener(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();
|
updateOldUIDislikesTextView();
|
||||||
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "setOldUILayoutDislikes failure", ex);
|
LogHelper.printException(() -> "setOldUILayoutDislikes failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void newVideoLoaded(@NonNull String videoId) {
|
|
||||||
try {
|
|
||||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
|
|
||||||
ReturnYouTubeDislike.newVideoLoaded(videoId);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LogHelper.printException(() -> "newVideoLoaded failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
@ -157,21 +172,153 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
return original;
|
return original;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replacement text to use for "Dislikes" while RYD is fetching.
|
||||||
|
*/
|
||||||
|
private static final Spannable SHORTS_LOADING_SPAN = new SpannableString("-");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dislikes TextViews used by Shorts.
|
||||||
|
*
|
||||||
|
* Multiple TextViews are loaded at once (for the prior and next videos to swipe to).
|
||||||
|
* Keep track of all of them, and later pick out the correct one based on their on screen position.
|
||||||
|
*/
|
||||||
|
private static final List<WeakReference<TextView>> shortsTextViewRefs = new ArrayList<>();
|
||||||
|
|
||||||
|
private static void clearRemovedShortsTextViews() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
shortsTextViewRefs.removeIf(ref -> ref.get() == null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException(); // YouTube requires Android N or greater
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point. Called when a Shorts dislike is updated.
|
||||||
|
* Handles update asynchronously, otherwise Shorts video will be frozen while the UI thread is blocked.
|
||||||
|
*
|
||||||
|
* @return if RYD is enabled and the TextView was updated
|
||||||
|
*/
|
||||||
|
public static boolean setShortsDislikes(@NonNull View likeDislikeView) {
|
||||||
|
try {
|
||||||
|
if (!SettingsEnum.RYD_ENABLED.getBoolean() || !SettingsEnum.RYD_SHORTS.getBoolean()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
LogHelper.printDebug(() -> "setShortsDislikes");
|
||||||
|
|
||||||
|
TextView textView = (TextView) likeDislikeView;
|
||||||
|
textView.setText(SHORTS_LOADING_SPAN); // Change 'Dislike' text to the loading text
|
||||||
|
shortsTextViewRefs.add(new WeakReference<>(textView));
|
||||||
|
|
||||||
|
if (likeDislikeView.isSelected() && isShortTextViewOnScreen(textView)) {
|
||||||
|
LogHelper.printDebug(() -> "Shorts dislike is already selected");
|
||||||
|
ReturnYouTubeDislike.setUserVote(Vote.DISLIKE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For the first short played, the shorts dislike hook is called after the video id hook.
|
||||||
|
// But for most other times this hook is called before the video id (which is not ideal).
|
||||||
|
// Must update the TextViews here, and also after the videoId changes.
|
||||||
|
updateOnScreenShortsTextViews(false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "setShortsDislikes failure", ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param forceUpdate if false, then only update the 'loading text views.
|
||||||
|
* If true, update all on screen text views.
|
||||||
|
*/
|
||||||
|
private static void updateOnScreenShortsTextViews(boolean forceUpdate) {
|
||||||
|
try {
|
||||||
|
clearRemovedShortsTextViews();
|
||||||
|
if (shortsTextViewRefs.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogHelper.printDebug(() -> "updateShortsTextViews");
|
||||||
|
String videoId = VideoInformation.getVideoId();
|
||||||
|
|
||||||
|
Runnable update = () -> {
|
||||||
|
Spanned shortsDislikesSpan = ReturnYouTubeDislike.getDislikeSpanForShort(SHORTS_LOADING_SPAN);
|
||||||
|
ReVancedUtils.runOnMainThreadNowOrLater(() -> {
|
||||||
|
if (!videoId.equals(VideoInformation.getVideoId())) {
|
||||||
|
// User swiped to new video before fetch completed
|
||||||
|
LogHelper.printDebug(() -> "Ignoring stale dislikes data for short: " + videoId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update text views that appear to be visible on screen.
|
||||||
|
// Only 1 will be the actual textview for the current Short,
|
||||||
|
// but discarded and not yet garbage collected views can remain.
|
||||||
|
// So must set the dislike span on all views that match.
|
||||||
|
for (WeakReference<TextView> textViewRef : shortsTextViewRefs) {
|
||||||
|
TextView textView = textViewRef.get();
|
||||||
|
if (textView == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (isShortTextViewOnScreen(textView)
|
||||||
|
&& (forceUpdate || textView.getText().toString().equals(SHORTS_LOADING_SPAN.toString()))) {
|
||||||
|
LogHelper.printDebug(() -> "Setting Shorts TextView to: " + shortsDislikesSpan);
|
||||||
|
textView.setText(shortsDislikesSpan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if (ReturnYouTubeDislike.fetchCompleted()) {
|
||||||
|
update.run(); // Network call is completed, no need to wait on background thread.
|
||||||
|
} else {
|
||||||
|
ReVancedUtils.runOnBackgroundThread(update);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "updateVisibleShortsTextViews failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a view is within the screen bounds.
|
||||||
|
*/
|
||||||
|
private static boolean isShortTextViewOnScreen(@NonNull View view) {
|
||||||
|
final int[] location = new int[2];
|
||||||
|
view.getLocationInWindow(location);
|
||||||
|
if (location[0] <= 0 && location[1] <= 0) { // Lower bound
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Rect windowRect = new Rect();
|
||||||
|
view.getWindowVisibleDisplayFrame(windowRect); // Upper bound
|
||||||
|
return location[0] < windowRect.width() && location[1] < windowRect.height();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*
|
|
||||||
* Called when a Shorts dislike Spanned is created.
|
|
||||||
*/
|
*/
|
||||||
public static Spanned onShortsComponentCreated(Spanned original) {
|
public static void newVideoLoaded(@NonNull String videoId) {
|
||||||
try {
|
try {
|
||||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
|
||||||
return original;
|
|
||||||
|
if (!videoId.equals(currentVideoId)) {
|
||||||
|
currentVideoId = videoId;
|
||||||
|
|
||||||
|
final boolean noneHiddenOrMinimized = PlayerType.getCurrent().isNoneHiddenOrMinimized();
|
||||||
|
if (noneHiddenOrMinimized && !SettingsEnum.RYD_SHORTS.getBoolean()) {
|
||||||
|
ReturnYouTubeDislike.setCurrentVideoId(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnYouTubeDislike.newVideoLoaded(videoId);
|
||||||
|
|
||||||
|
if (noneHiddenOrMinimized) {
|
||||||
|
// Shorts TextView hook can be called out of order with the video id hook.
|
||||||
|
// Must manually update again here.
|
||||||
|
updateOnScreenShortsTextViews(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ReturnYouTubeDislike.getDislikeSpanForShort(original);
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "onShortsComponentCreated failure", ex);
|
LogHelper.printException(() -> "newVideoLoaded failure", ex);
|
||||||
}
|
}
|
||||||
return original;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,10 +333,14 @@ public class ReturnYouTubeDislikePatch {
|
|||||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!SettingsEnum.RYD_SHORTS.getBoolean() && PlayerType.getCurrent().isNoneHiddenOrMinimized()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (Vote v : Vote.values()) {
|
for (Vote v : Vote.values()) {
|
||||||
if (v.value == vote) {
|
if (v.value == vote) {
|
||||||
ReturnYouTubeDislike.sendVote(v);
|
ReturnYouTubeDislike.sendVote(v);
|
||||||
|
|
||||||
updateOldUIDislikesTextView();
|
updateOldUIDislikesTextView();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,8 @@ package app.revanced.integrations.patches;
|
|||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
public class SeekbarTappingPatch {
|
public final class SeekbarTappingPatch {
|
||||||
|
public static boolean seekbarTappingEnabled() {
|
||||||
//Used by app.revanced.patches.youtube.interaction.seekbar.patch.EnableSeekbarTappingPatch
|
return SettingsEnum.SEEKBAR_TAPPING.getBoolean();
|
||||||
public static boolean isTapSeekingEnabled() {
|
|
||||||
return SettingsEnum.TAP_SEEKING_ENABLED.getBoolean();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ public class SpoofSignatureVerificationPatch {
|
|||||||
*/
|
*/
|
||||||
public static String overrideProtobufParameter(String originalValue) {
|
public static String overrideProtobufParameter(String originalValue) {
|
||||||
try {
|
try {
|
||||||
if (!SettingsEnum.SIGNATURE_SPOOFING.getBoolean()) {
|
if (!SettingsEnum.SPOOF_SIGNATURE_VERIFICATION.getBoolean()) {
|
||||||
return originalValue;
|
return originalValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,11 +101,11 @@ public class SpoofSignatureVerificationPatch {
|
|||||||
}
|
}
|
||||||
LogHelper.printDebug(() -> "YouTube HTTP status code: " + responseCode);
|
LogHelper.printDebug(() -> "YouTube HTTP status code: " + responseCode);
|
||||||
|
|
||||||
if (SettingsEnum.SIGNATURE_SPOOFING.getBoolean()) {
|
if (SettingsEnum.SPOOF_SIGNATURE_VERIFICATION.getBoolean()) {
|
||||||
return; // already enabled
|
return; // already enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum.SIGNATURE_SPOOFING.saveValue(true);
|
SettingsEnum.SPOOF_SIGNATURE_VERIFICATION.saveValue(true);
|
||||||
ReVancedUtils.showToastLong("Spoofing app signature to prevent playback issues");
|
ReVancedUtils.showToastLong("Spoofing app signature to prevent playback issues");
|
||||||
// it would be great if the video could be forcefully reloaded, but currently there is no code to do this
|
// it would be great if the video could be forcefully reloaded, but currently there is no code to do this
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ public class SpoofSignatureVerificationPatch {
|
|||||||
* @param sd function is not entirely clear
|
* @param sd function is not entirely clear
|
||||||
*/
|
*/
|
||||||
public static int[] getSubtitleWindowSettingsOverride(int ap, int ah, int av, boolean vs, boolean sd) {
|
public static int[] getSubtitleWindowSettingsOverride(int ap, int ah, int av, boolean vs, boolean sd) {
|
||||||
final boolean signatureSpoofing = SettingsEnum.SIGNATURE_SPOOFING.getBoolean();
|
final boolean signatureSpoofing = SettingsEnum.SPOOF_SIGNATURE_VERIFICATION.getBoolean();
|
||||||
if (SettingsEnum.DEBUG.getBoolean()) {
|
if (SettingsEnum.DEBUG.getBoolean()) {
|
||||||
if (ap != lastAp || ah != lastAh || av != lastAv || vs != lastVs || sd != lastSd) {
|
if (ap != lastAp || ah != lastAh || av != lastAv || vs != lastVs || sd != lastSd) {
|
||||||
LogHelper.printDebug(() -> "video: " + VideoInformation.getVideoId() + " spoof: " + signatureSpoofing
|
LogHelper.printDebug(() -> "video: " + VideoInformation.getVideoId() + " spoof: " + signatureSpoofing
|
||||||
|
@ -7,8 +7,7 @@ public class VideoAdsPatch {
|
|||||||
// Used by app.revanced.patches.youtube.ad.general.video.patch.VideoAdsPatch
|
// Used by app.revanced.patches.youtube.ad.general.video.patch.VideoAdsPatch
|
||||||
// depends on Whitelist patch (still needs to be written)
|
// depends on Whitelist patch (still needs to be written)
|
||||||
public static boolean shouldShowAds() {
|
public static boolean shouldShowAds() {
|
||||||
return !SettingsEnum.VIDEO_ADS_REMOVAL.getBoolean(); // TODO && Whitelist.shouldShowAds();
|
return !SettingsEnum.HIDE_VIDEO_ADS.getBoolean(); // TODO && Whitelist.shouldShowAds();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,10 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.integrations.patches.playback.speed.RememberPlaybackSpeedPatch;
|
import app.revanced.integrations.patches.playback.speed.RememberPlaybackSpeedPatch;
|
||||||
|
import app.revanced.integrations.shared.VideoState;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
@ -16,7 +18,7 @@ public final class VideoInformation {
|
|||||||
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
|
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
|
||||||
private static final String SEEK_METHOD_NAME = "seekTo";
|
private static final String SEEK_METHOD_NAME = "seekTo";
|
||||||
|
|
||||||
private static WeakReference<Object> playerController;
|
private static WeakReference<Object> playerControllerRef;
|
||||||
private static Method seekMethod;
|
private static Method seekMethod;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -30,17 +32,17 @@ public final class VideoInformation {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* Sets a reference to the YouTube playback controller.
|
|
||||||
*
|
*
|
||||||
* @param thisRef Reference to the player controller object.
|
* @param playerController player controller object.
|
||||||
*/
|
*/
|
||||||
public static void playerController_onCreateHook(final Object thisRef) {
|
public static void initialize(@NonNull Object playerController) {
|
||||||
playerController = new WeakReference<>(thisRef);
|
|
||||||
videoLength = 0;
|
|
||||||
videoTime = -1;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
seekMethod = thisRef.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE);
|
playerControllerRef = new WeakReference<>(Objects.requireNonNull(playerController));
|
||||||
|
videoTime = -1;
|
||||||
|
videoLength = 0;
|
||||||
|
playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED;
|
||||||
|
|
||||||
|
seekMethod = playerController.getClass().getMethod(SEEK_METHOD_NAME, Long.TYPE);
|
||||||
seekMethod.setAccessible(true);
|
seekMethod.setAccessible(true);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Failed to initialize", ex);
|
LogHelper.printException(() -> "Failed to initialize", ex);
|
||||||
@ -56,7 +58,6 @@ public final class VideoInformation {
|
|||||||
if (!videoId.equals(newlyLoadedVideoId)) {
|
if (!videoId.equals(newlyLoadedVideoId)) {
|
||||||
LogHelper.printDebug(() -> "New video id: " + newlyLoadedVideoId);
|
LogHelper.printDebug(() -> "New video id: " + newlyLoadedVideoId);
|
||||||
videoId = newlyLoadedVideoId;
|
videoId = newlyLoadedVideoId;
|
||||||
playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +125,7 @@ public final class VideoInformation {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
LogHelper.printDebug(() -> "Seeking to " + millisecond);
|
LogHelper.printDebug(() -> "Seeking to " + millisecond);
|
||||||
return (Boolean) seekMethod.invoke(playerController.get(), millisecond);
|
return (Boolean) seekMethod.invoke(playerControllerRef.get(), millisecond);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Failed to seek", ex);
|
LogHelper.printException(() -> "Failed to seek", ex);
|
||||||
return false;
|
return false;
|
||||||
@ -183,7 +184,12 @@ public final class VideoInformation {
|
|||||||
* @return If the playback is at the end of the video.
|
* @return If the playback is at the end of the video.
|
||||||
*
|
*
|
||||||
* If video is playing in the background with no video visible,
|
* If video is playing in the background with no video visible,
|
||||||
* this always returns false (even if the video is actually at the end)
|
* this always returns false (even if the video is actually at the end).
|
||||||
|
*
|
||||||
|
* This is equivalent to checking for {@link VideoState#ENDED},
|
||||||
|
* but can give a more up to date result for code calling from some hooks.
|
||||||
|
*
|
||||||
|
* @see VideoState
|
||||||
*/
|
*/
|
||||||
public static boolean isAtEndOfVideo() {
|
public static boolean isAtEndOfVideo() {
|
||||||
return videoTime > 0 && videoLength > 0 && videoTime >= videoLength;
|
return videoTime > 0 && videoLength > 0 && videoTime >= videoLength;
|
||||||
|
@ -0,0 +1,265 @@
|
|||||||
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
|
||||||
|
public final class AdsFilter extends Filter {
|
||||||
|
private final String[] exceptions;
|
||||||
|
|
||||||
|
private final CustomFilterGroup custom;
|
||||||
|
|
||||||
|
public AdsFilter() {
|
||||||
|
exceptions = new String[]{
|
||||||
|
"home_video_with_context",
|
||||||
|
"related_video_with_context",
|
||||||
|
"comment_thread", // skip filtering anything in the comments
|
||||||
|
"|comment.", // skip filtering anything in the comments replies
|
||||||
|
"library_recent_shelf",
|
||||||
|
};
|
||||||
|
|
||||||
|
custom = new CustomFilterGroup(
|
||||||
|
SettingsEnum.CUSTOM_FILTER,
|
||||||
|
SettingsEnum.CUSTOM_FILTER_STRINGS
|
||||||
|
);
|
||||||
|
|
||||||
|
final var communityPosts = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_COMMUNITY_POSTS,
|
||||||
|
"post_base_wrapper"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var communityGuidelines = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_COMMUNITY_GUIDELINES,
|
||||||
|
"community_guidelines"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var subscribersCommunityGuidelines = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES,
|
||||||
|
"sponsorships_comments_upsell"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
final var channelMemberShelf = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_CHANNEL_MEMBER_SHELF,
|
||||||
|
"member_recognition_shelf"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var compactBanner = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_COMPACT_BANNER,
|
||||||
|
"compact_banner"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var inFeedSurvey = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_FEED_SURVEY,
|
||||||
|
"in_feed_survey",
|
||||||
|
"slimline_survey"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var medicalPanel = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_MEDICAL_PANELS,
|
||||||
|
"medical_panel"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var paidContent = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_PAID_CONTENT,
|
||||||
|
"paid_content_overlay"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var merchandise = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_MERCHANDISE_BANNERS,
|
||||||
|
"product_carousel"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var infoPanel = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_HIDE_INFO_PANELS,
|
||||||
|
"publisher_transparency_panel",
|
||||||
|
"single_item_information_panel"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var latestPosts = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_HIDE_LATEST_POSTS,
|
||||||
|
"post_shelf"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var channelGuidelines = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_HIDE_CHANNEL_GUIDELINES,
|
||||||
|
"channel_guidelines_entry_banner"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var audioTrackButton = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_AUDIO_TRACK_BUTTON,
|
||||||
|
"multi_feed_icon_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var artistCard = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_ARTIST_CARDS,
|
||||||
|
"official_card"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var selfSponsor = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SELF_SPONSOR,
|
||||||
|
"cta_shelf_card"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var chapterTeaser = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_CHAPTER_TEASER,
|
||||||
|
"expandable_metadata",
|
||||||
|
"macro_markers_carousel"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var viewProducts = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_PRODUCTS_BANNER,
|
||||||
|
"product_item",
|
||||||
|
"products_in_video"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var webLinkPanel = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_WEB_SEARCH_RESULTS,
|
||||||
|
"web_link_panel"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var channelBar = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_CHANNEL_BAR,
|
||||||
|
"channel_bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var relatedVideos = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_RELATED_VIDEOS,
|
||||||
|
"fullscreen_related_videos"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var quickActions = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_QUICK_ACTIONS,
|
||||||
|
"quick_actions"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var imageShelf = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_IMAGE_SHELF,
|
||||||
|
"image_shelf"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var graySeparator = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_GRAY_SEPARATOR,
|
||||||
|
"cell_divider" // layout residue (gray line above the buttoned ad),
|
||||||
|
);
|
||||||
|
|
||||||
|
final var buttonedAd = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_BUTTONED_ADS,
|
||||||
|
"_buttoned_layout",
|
||||||
|
"full_width_square_image_layout",
|
||||||
|
"_ad_with",
|
||||||
|
"video_display_button_group_layout",
|
||||||
|
"landscape_image_wide_button_layout"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var generalAds = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_GENERAL_ADS,
|
||||||
|
"ads_video_with_context",
|
||||||
|
"banner_text_icon",
|
||||||
|
"square_image_layout",
|
||||||
|
"watch_metadata_app_promo",
|
||||||
|
"video_display_full_layout",
|
||||||
|
"hero_promo_image",
|
||||||
|
"statement_banner",
|
||||||
|
"carousel_footered_layout",
|
||||||
|
"text_image_button_layout",
|
||||||
|
"primetime_promo",
|
||||||
|
"product_details",
|
||||||
|
"full_width_portrait_image_layout",
|
||||||
|
"brand_video_shelf"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var movieAds = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_MOVIES_SECTION,
|
||||||
|
"browsy_bar",
|
||||||
|
"compact_movie",
|
||||||
|
"horizontal_movie_shelf",
|
||||||
|
"movie_and_show_upsell_card",
|
||||||
|
"compact_tvfilm_item",
|
||||||
|
"offer_module_root"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.pathFilterGroups.addAll(
|
||||||
|
generalAds,
|
||||||
|
buttonedAd,
|
||||||
|
channelBar,
|
||||||
|
communityPosts,
|
||||||
|
paidContent,
|
||||||
|
latestPosts,
|
||||||
|
movieAds,
|
||||||
|
chapterTeaser,
|
||||||
|
communityGuidelines,
|
||||||
|
quickActions,
|
||||||
|
relatedVideos,
|
||||||
|
compactBanner,
|
||||||
|
inFeedSurvey,
|
||||||
|
viewProducts,
|
||||||
|
medicalPanel,
|
||||||
|
merchandise,
|
||||||
|
infoPanel,
|
||||||
|
channelGuidelines,
|
||||||
|
audioTrackButton,
|
||||||
|
artistCard,
|
||||||
|
selfSponsor,
|
||||||
|
webLinkPanel,
|
||||||
|
imageShelf,
|
||||||
|
subscribersCommunityGuidelines,
|
||||||
|
channelMemberShelf
|
||||||
|
);
|
||||||
|
|
||||||
|
final var carouselAd = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_GENERAL_ADS,
|
||||||
|
"carousel_ad"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.identifierFilterGroups.addAll(
|
||||||
|
graySeparator,
|
||||||
|
carouselAd
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFiltered(final String path, final String identifier, final byte[] _protobufBufferArray) {
|
||||||
|
FilterResult result;
|
||||||
|
|
||||||
|
if (custom.isEnabled() && custom.check(path).isFiltered())
|
||||||
|
result = FilterResult.CUSTOM;
|
||||||
|
else if (ReVancedUtils.containsAny(path, exceptions))
|
||||||
|
result = FilterResult.EXCEPTION;
|
||||||
|
else if (pathFilterGroups.contains(path) || identifierFilterGroups.contains(identifier))
|
||||||
|
result = FilterResult.FILTERED;
|
||||||
|
else
|
||||||
|
result = FilterResult.UNFILTERED;
|
||||||
|
|
||||||
|
LogHelper.printDebug(() -> String.format("%s (ID: %s): %s", result.message, identifier, path));
|
||||||
|
|
||||||
|
return result.filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum FilterResult {
|
||||||
|
UNFILTERED(false, "Unfiltered"),
|
||||||
|
EXCEPTION(false, "Exception"),
|
||||||
|
FILTERED(true, "Filtered"),
|
||||||
|
CUSTOM(true, "Custom");
|
||||||
|
|
||||||
|
final Boolean filter;
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
FilterResult(boolean filter, String message) {
|
||||||
|
this.filter = filter;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the view, which shows ads in the homepage.
|
||||||
|
*
|
||||||
|
* @param view The view, which shows ads.
|
||||||
|
*/
|
||||||
|
public static void hideAdAttributionView(View view) {
|
||||||
|
ReVancedUtils.hideViewBy1dpUnderCondition(SettingsEnum.HIDE_GENERAL_ADS, view);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
final class ButtonsFilter extends Filter {
|
||||||
|
private final StringFilterGroup actionBarRule;
|
||||||
|
|
||||||
|
public ButtonsFilter() {
|
||||||
|
actionBarRule = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"video_action_bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
pathFilterGroups.addAll(
|
||||||
|
new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_LIKE_DISLIKE_BUTTON,
|
||||||
|
"|like_button",
|
||||||
|
"dislike_button"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_DOWNLOAD_BUTTON,
|
||||||
|
"download_button"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_PLAYLIST_BUTTON,
|
||||||
|
"save_to_playlist_button"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_CLIP_BUTTON,
|
||||||
|
"|clip_button.eml|"
|
||||||
|
),
|
||||||
|
new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_ACTION_BUTTONS,
|
||||||
|
"ContainerType|video_action_button",
|
||||||
|
"|CellType|CollectionType|CellType|ContainerType|button.eml|"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isEveryFilterGroupEnabled() {
|
||||||
|
for (StringFilterGroup rule : pathFilterGroups)
|
||||||
|
if (!rule.isEnabled()) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isFiltered(final String path, final String identifier, final byte[] _protobufBufferArray) {
|
||||||
|
if (isEveryFilterGroupEnabled())
|
||||||
|
if (actionBarRule.check(identifier).isFiltered()) return true;
|
||||||
|
|
||||||
|
return super.isFiltered(path, identifier, _protobufBufferArray);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
final class CommentsFilter extends Filter {
|
||||||
|
|
||||||
|
public CommentsFilter() {
|
||||||
|
var comments = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_COMMENTS_SECTION,
|
||||||
|
"video_metadata_carousel",
|
||||||
|
"_comments"
|
||||||
|
);
|
||||||
|
|
||||||
|
var previewComment = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_PREVIEW_COMMENT,
|
||||||
|
"|carousel_item",
|
||||||
|
"comments_entry_point_teaser",
|
||||||
|
"comments_entry_point_simplebox"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.pathFilterGroups.addAll(
|
||||||
|
comments,
|
||||||
|
previewComment
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,263 @@
|
|||||||
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
abstract class FilterGroup<T> {
|
||||||
|
final static class FilterGroupResult {
|
||||||
|
private final boolean filtered;
|
||||||
|
private final SettingsEnum setting;
|
||||||
|
|
||||||
|
public FilterGroupResult(final SettingsEnum setting, final boolean filtered) {
|
||||||
|
this.setting = setting;
|
||||||
|
this.filtered = filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SettingsEnum getSetting() {
|
||||||
|
return setting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFiltered() {
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final SettingsEnum setting;
|
||||||
|
protected final T[] filters;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a new filter group.
|
||||||
|
*
|
||||||
|
* @param setting The associated setting.
|
||||||
|
* @param filters The filters.
|
||||||
|
*/
|
||||||
|
@SafeVarargs
|
||||||
|
public FilterGroup(final SettingsEnum setting, final T... filters) {
|
||||||
|
this.setting = setting;
|
||||||
|
this.filters = filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return setting.getBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract FilterGroupResult check(final T stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
class StringFilterGroup extends FilterGroup<String> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link FilterGroup#FilterGroup(SettingsEnum, Object[])}
|
||||||
|
*/
|
||||||
|
public StringFilterGroup(final SettingsEnum setting, final String... filters) {
|
||||||
|
super(setting, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FilterGroupResult check(final String string) {
|
||||||
|
return new FilterGroupResult(setting, string != null && ReVancedUtils.containsAny(string, filters));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class CustomFilterGroup extends StringFilterGroup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link FilterGroup#FilterGroup(SettingsEnum, Object[])}
|
||||||
|
*/
|
||||||
|
public CustomFilterGroup(final SettingsEnum setting, final SettingsEnum filter) {
|
||||||
|
super(setting, filter.getString().split(","));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ByteArrayFilterGroup extends FilterGroup<byte[]> {
|
||||||
|
// Modified implementation from https://stackoverflow.com/a/1507813
|
||||||
|
private int indexOf(final byte[] data, final byte[] pattern) {
|
||||||
|
// Computes the failure function using a boot-strapping process,
|
||||||
|
// where the pattern is matched against itself.
|
||||||
|
|
||||||
|
final int[] failure = new int[pattern.length];
|
||||||
|
|
||||||
|
int j = 0;
|
||||||
|
for (int i = 1; i < pattern.length; i++) {
|
||||||
|
while (j > 0 && pattern[j] != pattern[i]) {
|
||||||
|
j = failure[j - 1];
|
||||||
|
}
|
||||||
|
if (pattern[j] == pattern[i]) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
failure[i] = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds the first occurrence of the pattern in the byte array using
|
||||||
|
// KMP matching algorithm.
|
||||||
|
|
||||||
|
j = 0;
|
||||||
|
if (data.length == 0) return -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
while (j > 0 && pattern[j] != data[i]) {
|
||||||
|
j = failure[j - 1];
|
||||||
|
}
|
||||||
|
if (pattern[j] == data[i]) {
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
if (j == pattern.length) {
|
||||||
|
return i - pattern.length + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link FilterGroup#FilterGroup(SettingsEnum, Object[])}
|
||||||
|
*/
|
||||||
|
public ByteArrayFilterGroup(final SettingsEnum setting, final byte[]... filters) {
|
||||||
|
super(setting, filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FilterGroupResult check(final byte[] bytes) {
|
||||||
|
var matched = false;
|
||||||
|
for (byte[] filter : filters) {
|
||||||
|
if (indexOf(bytes, filter) == -1) continue;
|
||||||
|
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
final var filtered = matched;
|
||||||
|
return new FilterGroupResult(setting, filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ByteArrayAsStringFilterGroup extends ByteArrayFilterGroup {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link ByteArrayFilterGroup#ByteArrayFilterGroup(SettingsEnum, byte[]...)}
|
||||||
|
*/
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
|
public ByteArrayAsStringFilterGroup(SettingsEnum setting, String... filters) {
|
||||||
|
super(setting, Arrays.stream(filters).map(String::getBytes).toArray(byte[][]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
|
||||||
|
private final ArrayList<T> filterGroups = new ArrayList<>();
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
protected final void addAll(final T... filterGroups) {
|
||||||
|
this.filterGroups.addAll(Arrays.asList(filterGroups));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return filterGroups.iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
|
@Override
|
||||||
|
public void forEach(@NonNull Consumer<? super T> action) {
|
||||||
|
filterGroups.forEach(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Spliterator<T> spliterator() {
|
||||||
|
return filterGroups.spliterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean contains(final V stack) {
|
||||||
|
for (T filterGroup : this) {
|
||||||
|
if (!filterGroup.isEnabled()) continue;
|
||||||
|
|
||||||
|
var result = filterGroup.check(stack);
|
||||||
|
if (result.isFiltered()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
|
||||||
|
}
|
||||||
|
|
||||||
|
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Filter {
|
||||||
|
final protected StringFilterGroupList pathFilterGroups = new StringFilterGroupList();
|
||||||
|
final protected StringFilterGroupList identifierFilterGroups = new StringFilterGroupList();
|
||||||
|
final protected ByteArrayFilterGroupList protobufBufferFilterGroups = new ByteArrayFilterGroupList();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given path, identifier or protobuf buffer is filtered by any {@link FilterGroup}.
|
||||||
|
*
|
||||||
|
* @return True if filtered, false otherwise.
|
||||||
|
*/
|
||||||
|
boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) {
|
||||||
|
if (pathFilterGroups.contains(path)) {
|
||||||
|
LogHelper.printDebug(() -> String.format("Filtered path: %s", path));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (identifierFilterGroups.contains(identifier)) {
|
||||||
|
LogHelper.printDebug(() -> String.format("Filtered identifier: %s", identifier));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protobufBufferFilterGroups.contains(protobufBufferArray)) {
|
||||||
|
LogHelper.printDebug(() -> "Filtered from protobuf-buffer");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class LithoFilterPatch {
|
||||||
|
private static final Filter[] filters = new Filter[]{
|
||||||
|
new AdsFilter(),
|
||||||
|
new ButtonsFilter(),
|
||||||
|
new CommentsFilter(),
|
||||||
|
new ShortsFilter()
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public static boolean filter(final StringBuilder pathBuilder, final String identifier, final ByteBuffer protobufBuffer) {
|
||||||
|
var path = pathBuilder.toString();
|
||||||
|
// It is assumed that protobufBuffer is empty as well in this case.
|
||||||
|
if (path.isEmpty()) return false;
|
||||||
|
|
||||||
|
LogHelper.printDebug(() -> String.format(
|
||||||
|
"Searching (ID: %s, Buffer-size: %s): %s",
|
||||||
|
identifier, protobufBuffer.remaining(), path
|
||||||
|
));
|
||||||
|
|
||||||
|
var protobufBufferArray = protobufBuffer.array();
|
||||||
|
|
||||||
|
// check if any filter-group
|
||||||
|
for (var filter : filters)
|
||||||
|
if (filter.isFiltered(path, identifier, protobufBufferArray)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package app.revanced.integrations.patches.components;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.utils.ReVancedUtils.hideViewBy1dpUnderCondition;
|
||||||
|
import static app.revanced.integrations.utils.ReVancedUtils.hideViewUnderCondition;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import com.google.android.libraries.youtube.rendering.ui.pivotbar.PivotBar;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public final class ShortsFilter extends Filter {
|
||||||
|
public static PivotBar pivotBar;
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
|
||||||
|
private final StringFilterGroup reelChannelBar = new StringFilterGroup(
|
||||||
|
null,
|
||||||
|
"reel_channel_bar"
|
||||||
|
);
|
||||||
|
|
||||||
|
public ShortsFilter() {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return;
|
||||||
|
|
||||||
|
final var thanksButton = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_THANKS_BUTTON,
|
||||||
|
"suggested_action"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var subscribeButton = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_SUBSCRIBE_BUTTON,
|
||||||
|
"subscribe_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var joinButton = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS_JOIN_BUTTON,
|
||||||
|
"sponsor_button"
|
||||||
|
);
|
||||||
|
|
||||||
|
final var shorts = new StringFilterGroup(
|
||||||
|
SettingsEnum.HIDE_SHORTS,
|
||||||
|
"shorts_shelf",
|
||||||
|
"inline_shorts",
|
||||||
|
"shorts_grid"
|
||||||
|
);
|
||||||
|
|
||||||
|
this.pathFilterGroups.addAll(joinButton, subscribeButton);
|
||||||
|
this.identifierFilterGroups.addAll(shorts, thanksButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isFiltered(final String path, final String identifier, final byte[] protobufBufferArray) {
|
||||||
|
// Filter the path only when reelChannelBar is visible.
|
||||||
|
if (reelChannelBar.check(path).isFiltered())
|
||||||
|
if (this.pathFilterGroups.contains(path)) return true;
|
||||||
|
|
||||||
|
return this.identifierFilterGroups.contains(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideShortsShelf(final View shortsShelfView) {
|
||||||
|
hideViewBy1dpUnderCondition(SettingsEnum.HIDE_SHORTS, shortsShelfView);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional components that have to be hidden by setting their visibility
|
||||||
|
|
||||||
|
public static void hideShortsCommentsButton(final View commentsButtonView) {
|
||||||
|
hideViewUnderCondition(SettingsEnum.HIDE_SHORTS_COMMENTS_BUTTON, commentsButtonView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideShortsRemixButton(final View remixButtonView) {
|
||||||
|
hideViewUnderCondition(SettingsEnum.HIDE_SHORTS_REMIX_BUTTON, remixButtonView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideShortsShareButton(final View shareButtonView) {
|
||||||
|
hideViewUnderCondition(SettingsEnum.HIDE_SHORTS_SHARE_BUTTON, shareButtonView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hideNavigationBar() {
|
||||||
|
if (!SettingsEnum.HIDE_SHORTS_NAVIGATION_BAR.getBoolean()) return;
|
||||||
|
if (pivotBar == null) return;
|
||||||
|
|
||||||
|
pivotBar.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static View hideNavigationBar(final View navigationBarView) {
|
||||||
|
if (SettingsEnum.HIDE_SHORTS_NAVIGATION_BAR.getBoolean())
|
||||||
|
return null; // Hides the navigation bar.
|
||||||
|
|
||||||
|
return navigationBarView;
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,7 @@ import app.revanced.integrations.utils.LogHelper;
|
|||||||
public class OldQualityLayoutPatch {
|
public class OldQualityLayoutPatch {
|
||||||
public static void showOldQualityMenu(ListView listView)
|
public static void showOldQualityMenu(ListView listView)
|
||||||
{
|
{
|
||||||
if (!SettingsEnum.OLD_STYLE_VIDEO_QUALITY_PLAYER_SETTINGS.getBoolean()) return;
|
if (!SettingsEnum.SHOW_OLD_VIDEO_MENU.getBoolean()) return;
|
||||||
|
|
||||||
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
|
listView.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,7 +2,6 @@ package app.revanced.integrations.patches.playback.quality;
|
|||||||
|
|
||||||
import static app.revanced.integrations.utils.ReVancedUtils.NetworkType;
|
import static app.revanced.integrations.utils.ReVancedUtils.NetworkType;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@ -20,12 +19,10 @@ public class RememberVideoQualityPatch {
|
|||||||
private static final SettingsEnum mobileQualitySetting = SettingsEnum.VIDEO_QUALITY_DEFAULT_MOBILE;
|
private static final SettingsEnum mobileQualitySetting = SettingsEnum.VIDEO_QUALITY_DEFAULT_MOBILE;
|
||||||
|
|
||||||
private static boolean qualityNeedsUpdating;
|
private static boolean qualityNeedsUpdating;
|
||||||
@Nullable
|
|
||||||
private static String currentVideoId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the user selected a new quality from the flyout menu,
|
* If the user selected a new quality from the flyout menu,
|
||||||
* and {@link SettingsEnum#VIDEO_QUALITY_REMEMBER_LAST_SELECTED} is enabled.
|
* and {@link SettingsEnum#REMEMBER_VIDEO_QUALITY_LAST_SELECTED} is enabled.
|
||||||
*/
|
*/
|
||||||
private static boolean userChangedDefaultQuality;
|
private static boolean userChangedDefaultQuality;
|
||||||
|
|
||||||
@ -91,7 +88,7 @@ public class RememberVideoQualityPatch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LogHelper.printDebug(() -> "VideoId: " + currentVideoId + " videoQualities: " + videoQualities);
|
LogHelper.printDebug(() -> "videoQualities: " + videoQualities);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userChangedDefaultQuality) {
|
if (userChangedDefaultQuality) {
|
||||||
@ -113,15 +110,25 @@ public class RememberVideoQualityPatch {
|
|||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the desired quality index is equal to the original index,
|
||||||
|
// then the video is already set to the desired default quality.
|
||||||
|
//
|
||||||
|
// The method could return here, but the UI video quality flyout will still
|
||||||
|
// show 'Auto' (ie: Auto (480p))
|
||||||
|
// It appears that "Auto" picks the resolution on video load,
|
||||||
|
// and it does not appear to change the resolution during playback.
|
||||||
|
//
|
||||||
|
// To prevent confusion, set the video index anyways (even if it matches the existing index)
|
||||||
|
// As that will force the UI picker to not display "Auto" which may confuse the user.
|
||||||
if (qualityIndexToUse == originalQualityIndex) {
|
if (qualityIndexToUse == originalQualityIndex) {
|
||||||
LogHelper.printDebug(() -> "Video is already preferred quality: " + preferredQuality);
|
LogHelper.printDebug(() -> "Video is already preferred quality: " + preferredQuality);
|
||||||
return originalQualityIndex;
|
} else {
|
||||||
|
final int qualityToUseLog = qualityToUse;
|
||||||
|
LogHelper.printDebug(() -> "Quality changed from: "
|
||||||
|
+ videoQualities.get(originalQualityIndex) + " to: " + qualityToUseLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int qualityToUseLog = qualityToUse;
|
|
||||||
LogHelper.printDebug(() -> "Quality changed from: "
|
|
||||||
+ videoQualities.get(originalQualityIndex) + " to: " + qualityToUseLog);
|
|
||||||
|
|
||||||
Method m = qInterface.getClass().getMethod(qIndexMethod, Integer.TYPE);
|
Method m = qInterface.getClass().getMethod(qIndexMethod, Integer.TYPE);
|
||||||
m.invoke(qInterface, qualityToUse);
|
m.invoke(qInterface, qualityToUse);
|
||||||
return qualityIndexToUse;
|
return qualityIndexToUse;
|
||||||
@ -135,7 +142,7 @@ public class RememberVideoQualityPatch {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void userChangedQuality(int selectedQuality) {
|
public static void userChangedQuality(int selectedQuality) {
|
||||||
if (!SettingsEnum.VIDEO_QUALITY_REMEMBER_LAST_SELECTED.getBoolean()) return;
|
if (!SettingsEnum.REMEMBER_VIDEO_QUALITY_LAST_SELECTED.getBoolean()) return;
|
||||||
|
|
||||||
userSelectedQualityIndex = selectedQuality;
|
userSelectedQualityIndex = selectedQuality;
|
||||||
userChangedDefaultQuality = true;
|
userChangedDefaultQuality = true;
|
||||||
@ -144,25 +151,9 @@ public class RememberVideoQualityPatch {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void newVideoStarted(@NonNull String videoId) {
|
public static void newVideoStarted(Object ignoredPlayerController) {
|
||||||
// The same videoId can be passed in multiple times for a single video playback.
|
LogHelper.printDebug(() -> "newVideoStarted");
|
||||||
// Such as closing and opening the app, and sometimes when turning off/on the device screen.
|
|
||||||
//
|
|
||||||
// Known limitation, if:
|
|
||||||
// 1. a default video quality exists, and remember quality is turned off
|
|
||||||
// 2. user opens a video
|
|
||||||
// 3. user changes the video quality
|
|
||||||
// 4. user turns off then on the device screen (or does anything else that triggers the video id hook)
|
|
||||||
// result: the video quality of the current video will revert back to the saved default
|
|
||||||
//
|
|
||||||
// qualityNeedsUpdating could be set only when the videoId changes
|
|
||||||
// but then if the user closes and re-opens the same video the default video quality will not be applied.
|
|
||||||
LogHelper.printDebug(() -> "newVideoStarted: " + videoId);
|
|
||||||
qualityNeedsUpdating = true;
|
qualityNeedsUpdating = true;
|
||||||
|
videoQualities = null;
|
||||||
if (!videoId.equals(currentVideoId)) {
|
|
||||||
currentVideoId = videoId;
|
|
||||||
videoQualities = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,103 @@
|
|||||||
package app.revanced.integrations.patches.playback.speed;
|
package app.revanced.integrations.patches.playback.speed;
|
||||||
|
|
||||||
|
import android.preference.ListPreference;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public class CustomVideoSpeedPatch {
|
public class CustomVideoSpeedPatch {
|
||||||
/**
|
/**
|
||||||
* Default playback speeds offered by YouTube.
|
* Maximum playback speed, exclusive value. Custom speeds must be less than this value.
|
||||||
* Values are also used by {@link RememberPlaybackSpeedPatch}.
|
|
||||||
*
|
|
||||||
* If custom video speed is applied,
|
|
||||||
* then this array is overwritten by the patch with custom speeds
|
|
||||||
*/
|
*/
|
||||||
public static final float[] videoSpeeds = {0.25f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
|
public static final float MAXIMUM_PLAYBACK_SPEED = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom playback speeds.
|
||||||
|
*/
|
||||||
|
public static float[] customVideoSpeeds;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Minimum value of {@link #customVideoSpeeds}
|
||||||
|
*/
|
||||||
|
public static float minVideoSpeed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maxium value of {@link #customVideoSpeeds}
|
||||||
|
*/
|
||||||
|
public static float maxVideoSpeed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PreferenceList entries and values, of all available playback speeds.
|
||||||
|
*/
|
||||||
|
private static String[] preferenceListEntries, preferenceListEntryValues;
|
||||||
|
|
||||||
|
static {
|
||||||
|
loadSpeeds();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetCustomSpeeds(@NonNull String toastMessage) {
|
||||||
|
ReVancedUtils.showToastLong(toastMessage);
|
||||||
|
SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.saveValue(SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadSpeeds() {
|
||||||
|
try {
|
||||||
|
String[] speedStrings = SettingsEnum.CUSTOM_PLAYBACK_SPEEDS.getString().split("\\s+");
|
||||||
|
Arrays.sort(speedStrings);
|
||||||
|
if (speedStrings.length == 0) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
customVideoSpeeds = new float[speedStrings.length];
|
||||||
|
for (int i = 0, length = speedStrings.length; i < length; i++) {
|
||||||
|
final float speed = Float.parseFloat(speedStrings[i]);
|
||||||
|
if (speed <= 0 || arrayContains(customVideoSpeeds, speed)) {
|
||||||
|
throw new IllegalArgumentException();
|
||||||
|
}
|
||||||
|
if (speed >= MAXIMUM_PLAYBACK_SPEED) {
|
||||||
|
resetCustomSpeeds("Custom speeds must be less than " + MAXIMUM_PLAYBACK_SPEED
|
||||||
|
+ ". Using default values.");
|
||||||
|
loadSpeeds();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
minVideoSpeed = Math.min(minVideoSpeed, speed);
|
||||||
|
maxVideoSpeed = Math.max(maxVideoSpeed, speed);
|
||||||
|
customVideoSpeeds[i] = speed;
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printInfo(() -> "parse error", ex);
|
||||||
|
resetCustomSpeeds("Invalid custom video speeds. Using default values.");
|
||||||
|
loadSpeeds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean arrayContains(float[] array, float value) {
|
||||||
|
for (float arrayValue : array) {
|
||||||
|
if (arrayValue == value) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a settings preference list with the available playback speeds.
|
||||||
|
*/
|
||||||
|
public static void initializeListPreference(ListPreference preference) {
|
||||||
|
if (preferenceListEntries == null) {
|
||||||
|
preferenceListEntries = new String[customVideoSpeeds.length];
|
||||||
|
preferenceListEntryValues = new String[customVideoSpeeds.length];
|
||||||
|
int i = 0;
|
||||||
|
for (float speed : customVideoSpeeds) {
|
||||||
|
String speedString = String.valueOf(speed);
|
||||||
|
preferenceListEntries[i] = speedString + "x";
|
||||||
|
preferenceListEntryValues[i] = speedString;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preference.setEntries(preferenceListEntries);
|
||||||
|
preference.setEntryValues(preferenceListEntryValues);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +1,17 @@
|
|||||||
package app.revanced.integrations.patches.playback.speed;
|
package app.revanced.integrations.patches.playback.speed;
|
||||||
|
|
||||||
import android.preference.ListPreference;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import app.revanced.integrations.patches.VideoInformation;
|
import app.revanced.integrations.patches.VideoInformation;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public final class RememberPlaybackSpeedPatch {
|
public final class RememberPlaybackSpeedPatch {
|
||||||
|
|
||||||
/**
|
|
||||||
* PreferenceList entries and values, of all available playback speeds.
|
|
||||||
*/
|
|
||||||
private static String[] preferenceListEntries, preferenceListEntryValues;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static String currentVideoId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
* Called when a new video loads.
|
|
||||||
*/
|
*/
|
||||||
public static void newVideoLoaded(@NonNull String videoId) {
|
public static void newVideoStarted(Object ignoredPlayerController) {
|
||||||
if (videoId.equals(currentVideoId)) {
|
LogHelper.printDebug(() -> "newVideoStarted");
|
||||||
return;
|
|
||||||
}
|
|
||||||
currentVideoId = videoId;
|
|
||||||
VideoInformation.overridePlaybackSpeed(SettingsEnum.PLAYBACK_SPEED_DEFAULT.getFloat());
|
VideoInformation.overridePlaybackSpeed(SettingsEnum.PLAYBACK_SPEED_DEFAULT.getFloat());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,7 +22,7 @@ public final class RememberPlaybackSpeedPatch {
|
|||||||
* @param playbackSpeed The playback speed the user selected
|
* @param playbackSpeed The playback speed the user selected
|
||||||
*/
|
*/
|
||||||
public static void userSelectedPlaybackSpeed(float playbackSpeed) {
|
public static void userSelectedPlaybackSpeed(float playbackSpeed) {
|
||||||
if (SettingsEnum.PLAYBACK_SPEED_REMEMBER_LAST_SELECTED.getBoolean()) {
|
if (SettingsEnum.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED.getBoolean()) {
|
||||||
SettingsEnum.PLAYBACK_SPEED_DEFAULT.saveValue(playbackSpeed);
|
SettingsEnum.PLAYBACK_SPEED_DEFAULT.saveValue(playbackSpeed);
|
||||||
ReVancedUtils.showToastLong("Changed default speed to: " + playbackSpeed + "x");
|
ReVancedUtils.showToastLong("Changed default speed to: " + playbackSpeed + "x");
|
||||||
}
|
}
|
||||||
@ -52,26 +36,4 @@ public final class RememberPlaybackSpeedPatch {
|
|||||||
return VideoInformation.getPlaybackSpeed();
|
return VideoInformation.getPlaybackSpeed();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a settings preference list.
|
|
||||||
*
|
|
||||||
* Normally this is done during patching by creating a static xml preference list,
|
|
||||||
* but the available playback speeds differ depending if {@link CustomVideoSpeedPatch} is applied or not.
|
|
||||||
*/
|
|
||||||
public static void initializeListPreference(ListPreference preference) {
|
|
||||||
if (preferenceListEntries == null) {
|
|
||||||
float[] videoSpeeds = CustomVideoSpeedPatch.videoSpeeds;
|
|
||||||
preferenceListEntries = new String[videoSpeeds.length];
|
|
||||||
preferenceListEntryValues = new String[videoSpeeds.length];
|
|
||||||
int i = 0;
|
|
||||||
for (float speed : videoSpeeds) {
|
|
||||||
String speedString = String.valueOf(speed);
|
|
||||||
preferenceListEntries[i] = speedString + "x";
|
|
||||||
preferenceListEntryValues[i] = speedString;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preference.setEntries(preferenceListEntries);
|
|
||||||
preference.setEntryValues(preferenceListEntryValues);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
package app.revanced.integrations.patches.theme;
|
||||||
|
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.ColorFilter;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.integrations.patches.HideSeekbarPatch;
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by {@link SeekbarColorPatch} change the color of the seekbar.
|
||||||
|
* and {@link HideSeekbarPatch} to hide the seekbar of the feed and watch history.
|
||||||
|
*/
|
||||||
|
public class ProgressBarDrawable extends Drawable {
|
||||||
|
|
||||||
|
private final Paint paint = new Paint();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(@NonNull Canvas canvas) {
|
||||||
|
if (SettingsEnum.HIDE_SEEKBAR.getBoolean()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
paint.setColor(SeekbarColorPatch.getCustomSeekbarColor());
|
||||||
|
canvas.drawRect(getBounds(), paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAlpha(int alpha) {
|
||||||
|
paint.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
||||||
|
paint.setColorFilter(colorFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOpacity() {
|
||||||
|
return PixelFormat.TRANSLUCENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package app.revanced.integrations.patches.theme;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
public final class SeekbarColorPatch {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default color of seekbar.
|
||||||
|
*/
|
||||||
|
private static final int ORIGINAL_SEEKBAR_COLOR = 0xFFFF0000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default YouTube seekbar color brightness.
|
||||||
|
*/
|
||||||
|
private static final float ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color value of {@link SettingsEnum#SEEKBAR_COLOR}
|
||||||
|
*/
|
||||||
|
private static int customSeekbarColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom seekbar hue, saturation, and brightness values.
|
||||||
|
*/
|
||||||
|
private static final float[] customSeekbarColorHSV = new float[3];
|
||||||
|
|
||||||
|
static {
|
||||||
|
float[] hsv = new float[3];
|
||||||
|
Color.colorToHSV(ORIGINAL_SEEKBAR_COLOR, hsv);
|
||||||
|
ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS = hsv[2];
|
||||||
|
|
||||||
|
loadCustomSeekbarColorHSV();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void loadCustomSeekbarColorHSV() {
|
||||||
|
try {
|
||||||
|
customSeekbarColor = Color.parseColor(SettingsEnum.SEEKBAR_COLOR.getString());
|
||||||
|
Color.colorToHSV(customSeekbarColor, customSeekbarColorHSV);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ReVancedUtils.showToastShort("Invalid seekbar color value. Using default value.");
|
||||||
|
SettingsEnum.SEEKBAR_COLOR.saveValue(SettingsEnum.SEEKBAR_COLOR.defaultValue);
|
||||||
|
loadCustomSeekbarColorHSV();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getCustomSeekbarColor() {
|
||||||
|
return customSeekbarColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* Overrides color when seekbar is clicked, and all Litho components that use the YouTube seekbar color.
|
||||||
|
*/
|
||||||
|
public static int getSeekbarColorOverride(int colorValue) {
|
||||||
|
return colorValue == ORIGINAL_SEEKBAR_COLOR
|
||||||
|
? getSeekbarColorValue(ORIGINAL_SEEKBAR_COLOR)
|
||||||
|
: colorValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injection point.
|
||||||
|
*
|
||||||
|
* If {@link SettingsEnum#HIDE_SEEKBAR} is enabled, this returns a fully transparent color.
|
||||||
|
*
|
||||||
|
* Otherwise the original color is changed to the custom seekbar color, while retaining
|
||||||
|
* the brightness and alpha changes of the parameter value compared to the original seekbar color.
|
||||||
|
*/
|
||||||
|
public static int getSeekbarColorValue(int originalColor) {
|
||||||
|
try {
|
||||||
|
if (SettingsEnum.HIDE_SEEKBAR.getBoolean()) {
|
||||||
|
return 0x00000000;
|
||||||
|
}
|
||||||
|
if (customSeekbarColor == ORIGINAL_SEEKBAR_COLOR) {
|
||||||
|
return originalColor; // Nothing to do
|
||||||
|
}
|
||||||
|
final int alphaDifference = Color.alpha(originalColor) - Color.alpha(ORIGINAL_SEEKBAR_COLOR);
|
||||||
|
|
||||||
|
// The seekbar uses the same color but different brightness for different situations.
|
||||||
|
float[] hsv = new float[3];
|
||||||
|
Color.colorToHSV(originalColor, hsv);
|
||||||
|
final float brightnessDifference = hsv[2] - ORIGINAL_SEEKBAR_COLOR_BRIGHTNESS;
|
||||||
|
|
||||||
|
// Apply the brightness difference to the custom seekbar color.
|
||||||
|
hsv[0] = customSeekbarColorHSV[0];
|
||||||
|
hsv[1] = customSeekbarColorHSV[1];
|
||||||
|
hsv[2] = clamp(customSeekbarColorHSV[2] + brightnessDifference, 0, 1);
|
||||||
|
|
||||||
|
final int replacementAlpha = clamp(Color.alpha(customSeekbarColor) + alphaDifference, 0, 255);
|
||||||
|
final int replacementColor = Color.HSVToColor(replacementAlpha, hsv);
|
||||||
|
LogHelper.printDebug(() -> String.format("Original color: #%08X replacement color: #%08X",
|
||||||
|
originalColor, replacementColor));
|
||||||
|
return replacementColor;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "getSeekbarColorValue failure", ex);
|
||||||
|
return originalColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int clamp(int value, int lower, int upper) {
|
||||||
|
return Math.max(lower, Math.min(value, upper));
|
||||||
|
}
|
||||||
|
|
||||||
|
static float clamp(float value, float lower, float upper) {
|
||||||
|
return Math.max(lower, Math.min(value, upper));
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
package app.revanced.integrations.patches.theme;
|
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
public final class ThemePatch {
|
|
||||||
public static final int DEFAULT_SEEKBAR_COLOR = 0xffff0000;
|
|
||||||
|
|
||||||
public static final int ORIGINAL_SEEKBAR_CLICKED_COLOR = -65536;
|
|
||||||
|
|
||||||
private static void resetSeekbarColor() {
|
|
||||||
ReVancedUtils.showToastShort("Invalid seekbar color value. Using default value.");
|
|
||||||
SettingsEnum.SEEKBAR_COLOR.saveValue("#" + Integer.toHexString(DEFAULT_SEEKBAR_COLOR));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getSeekbarClickedColorValue(final int colorValue) {
|
|
||||||
// YouTube uses a specific color when the seekbar is clicked. Override in that case.
|
|
||||||
return colorValue == ORIGINAL_SEEKBAR_CLICKED_COLOR ? getSeekbarColorValue() : colorValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int getSeekbarColorValue() {
|
|
||||||
try {
|
|
||||||
return Color.parseColor(SettingsEnum.SEEKBAR_COLOR.getString());
|
|
||||||
} catch (IllegalArgumentException exception) {
|
|
||||||
resetSeekbarColor();
|
|
||||||
return DEFAULT_SEEKBAR_COLOR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -25,8 +25,11 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
@ -45,13 +48,47 @@ import app.revanced.integrations.utils.ThemeHelper;
|
|||||||
* Because Litho creates spans using multiple threads, this entire class supports multithreading as well.
|
* Because Litho creates spans using multiple threads, this entire class supports multithreading as well.
|
||||||
*/
|
*/
|
||||||
public class ReturnYouTubeDislike {
|
public class ReturnYouTubeDislike {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple wrapper to cache a Future.
|
||||||
|
*/
|
||||||
|
private static class RYDCachedFetch {
|
||||||
|
/**
|
||||||
|
* How long to retain cached RYD fetches.
|
||||||
|
*/
|
||||||
|
static final long CACHE_TIMEOUT_MILLISECONDS = 4 * 60 * 1000; // 4 Minutes
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
final Future<RYDVoteData> future;
|
||||||
|
final String videoId;
|
||||||
|
final long timeFetched;
|
||||||
|
RYDCachedFetch(@NonNull Future<RYDVoteData> future, @NonNull String videoId) {
|
||||||
|
this.future = Objects.requireNonNull(future);
|
||||||
|
this.videoId = Objects.requireNonNull(videoId);
|
||||||
|
this.timeFetched = System.currentTimeMillis();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isExpired(long now) {
|
||||||
|
return (now - timeFetched) > CACHE_TIMEOUT_MILLISECONDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean futureInProgressOrFinishedSuccessfully() {
|
||||||
|
try {
|
||||||
|
return !future.isDone() || future.get(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH, TimeUnit.MILLISECONDS) != null;
|
||||||
|
} catch (ExecutionException | InterruptedException | TimeoutException ex) {
|
||||||
|
LogHelper.printInfo(() -> "failed to lookup cache", ex); // will never happen
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maximum amount of time to block the UI from updates while waiting for network call to complete.
|
* Maximum amount of time to block the UI from updates while waiting for network call to complete.
|
||||||
*
|
*
|
||||||
* Must be less than 5 seconds, as per:
|
* Must be less than 5 seconds, as per:
|
||||||
* https://developer.android.com/topic/performance/vitals/anr
|
* https://developer.android.com/topic/performance/vitals/anr
|
||||||
*/
|
*/
|
||||||
private static final long MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE = 4000;
|
private static final long MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH = 4000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique placeholder character, used to detect if a segmented span already has dislikes added to it.
|
* Unique placeholder character, used to detect if a segmented span already has dislikes added to it.
|
||||||
@ -59,6 +96,12 @@ public class ReturnYouTubeDislike {
|
|||||||
*/
|
*/
|
||||||
private static final char MIDDLE_SEPARATOR_CHARACTER = '\u2009'; // 'narrow space' character
|
private static final char MIDDLE_SEPARATOR_CHARACTER = '\u2009'; // 'narrow space' character
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cached lookup of RYD fetches.
|
||||||
|
*/
|
||||||
|
@GuardedBy("videoIdLockObject")
|
||||||
|
private static final Map<String, RYDCachedFetch> futureCache = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to send votes, one by one, in the same order the user created them.
|
* Used to send votes, one by one, in the same order the user created them.
|
||||||
*/
|
*/
|
||||||
@ -85,6 +128,13 @@ public class ReturnYouTubeDislike {
|
|||||||
@GuardedBy("videoIdLockObject")
|
@GuardedBy("videoIdLockObject")
|
||||||
private static Future<RYDVoteData> voteFetchFuture;
|
private static Future<RYDVoteData> voteFetchFuture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional current vote status of the UI. Used to apply a user vote that was done on a previous video viewing.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@GuardedBy("videoIdLockObject")
|
||||||
|
private static Vote userVote;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Original dislike span, before modifications.
|
* Original dislike span, before modifications.
|
||||||
*/
|
*/
|
||||||
@ -135,13 +185,25 @@ public class ReturnYouTubeDislike {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void setCurrentVideoId(@Nullable String videoId) {
|
public static void setCurrentVideoId(@Nullable String videoId) {
|
||||||
synchronized (videoIdLockObject) {
|
synchronized (videoIdLockObject) {
|
||||||
if (videoId == null && currentVideoId != null) {
|
if (videoId == null && currentVideoId != null) {
|
||||||
LogHelper.printDebug(() -> "Clearing data");
|
LogHelper.printDebug(() -> "Clearing data");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
final long now = System.currentTimeMillis();
|
||||||
|
futureCache.values().removeIf(value -> {
|
||||||
|
final boolean expired = value.isExpired(now);
|
||||||
|
if (expired) LogHelper.printDebug(() -> "Removing expired fetch: " + value.videoId);
|
||||||
|
return expired;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(); // YouTube requires Android N or greater
|
||||||
|
}
|
||||||
currentVideoId = videoId;
|
currentVideoId = videoId;
|
||||||
dislikeDataIsShort = false;
|
dislikeDataIsShort = false;
|
||||||
|
userVote = null;
|
||||||
voteFetchFuture = null;
|
voteFetchFuture = null;
|
||||||
originalDislikeSpan = null;
|
originalDislikeSpan = null;
|
||||||
replacementLikeDislikeSpan = null;
|
replacementLikeDislikeSpan = null;
|
||||||
@ -154,7 +216,7 @@ public class ReturnYouTubeDislike {
|
|||||||
public static void clearCache() {
|
public static void clearCache() {
|
||||||
synchronized (videoIdLockObject) {
|
synchronized (videoIdLockObject) {
|
||||||
if (replacementLikeDislikeSpan != null) {
|
if (replacementLikeDislikeSpan != null) {
|
||||||
LogHelper.printDebug(() -> "Clearing cache");
|
LogHelper.printDebug(() -> "Clearing replacement spans");
|
||||||
}
|
}
|
||||||
replacementLikeDislikeSpan = null;
|
replacementLikeDislikeSpan = null;
|
||||||
}
|
}
|
||||||
@ -177,12 +239,6 @@ public class ReturnYouTubeDislike {
|
|||||||
public static void newVideoLoaded(@NonNull String videoId) {
|
public static void newVideoLoaded(@NonNull String videoId) {
|
||||||
Objects.requireNonNull(videoId);
|
Objects.requireNonNull(videoId);
|
||||||
|
|
||||||
PlayerType currentPlayerType = PlayerType.getCurrent();
|
|
||||||
if (currentPlayerType == PlayerType.INLINE_MINIMAL) {
|
|
||||||
LogHelper.printDebug(() -> "Ignoring inline playback of video: " + videoId);
|
|
||||||
setCurrentVideoId(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
synchronized (videoIdLockObject) {
|
synchronized (videoIdLockObject) {
|
||||||
if (videoId.equals(currentVideoId)) {
|
if (videoId.equals(currentVideoId)) {
|
||||||
return; // already loaded
|
return; // already loaded
|
||||||
@ -192,17 +248,23 @@ public class ReturnYouTubeDislike {
|
|||||||
setCurrentVideoId(null);
|
setCurrentVideoId(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
PlayerType currentPlayerType = PlayerType.getCurrent();
|
||||||
LogHelper.printDebug(() -> "New video loaded: " + videoId + " playerType: " + currentPlayerType);
|
LogHelper.printDebug(() -> "New video loaded: " + videoId + " playerType: " + currentPlayerType);
|
||||||
setCurrentVideoId(videoId);
|
setCurrentVideoId(videoId);
|
||||||
|
|
||||||
// If a Short is opened while a regular video is on screen, this will incorrectly set this as false.
|
// If a Short is opened while a regular video is on screen, this will incorrectly set this as false.
|
||||||
// But this check is needed to fix unusual situations of opening/closing the app
|
// But this check is needed to fix unusual situations of opening/closing the app
|
||||||
// while both a regular video and a short are on screen.
|
// while both a regular video and a short are on screen.
|
||||||
dislikeDataIsShort = PlayerType.getCurrent().isNoneOrHidden();
|
dislikeDataIsShort = currentPlayerType.isNoneHiddenOrMinimized();
|
||||||
|
|
||||||
// No need to wrap the call in a try/catch,
|
RYDCachedFetch entry = futureCache.get(videoId);
|
||||||
// as any exceptions are propagated out in the later Future#Get call.
|
if (entry != null && entry.futureInProgressOrFinishedSuccessfully()) {
|
||||||
|
LogHelper.printDebug(() -> "Using cached RYD fetch: "+ entry.videoId);
|
||||||
|
voteFetchFuture = entry.future;
|
||||||
|
return;
|
||||||
|
}
|
||||||
voteFetchFuture = ReVancedUtils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
|
voteFetchFuture = ReVancedUtils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
|
||||||
|
futureCache.put(videoId, new RYDCachedFetch(voteFetchFuture, videoId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,13 +302,28 @@ public class ReturnYouTubeDislike {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private static Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned oldSpannable, boolean isSegmentedButton) {
|
private static Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned oldSpannable, boolean isSegmentedButton) {
|
||||||
try {
|
try {
|
||||||
|
Future<RYDVoteData> fetchFuture = getVoteFetchFuture();
|
||||||
|
if (fetchFuture == null) {
|
||||||
|
LogHelper.printDebug(() -> "fetch future not available (user enabled RYD while video was playing?)");
|
||||||
|
return oldSpannable;
|
||||||
|
}
|
||||||
|
// Absolutely cannot be holding any lock during get().
|
||||||
|
RYDVoteData votingData = fetchFuture.get(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH, TimeUnit.MILLISECONDS);
|
||||||
|
if (votingData == null) {
|
||||||
|
LogHelper.printDebug(() -> "Cannot add dislike to UI (RYD data not available)");
|
||||||
|
return oldSpannable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must check against existing replacements, after the fetch,
|
||||||
|
// otherwise concurrent threads can create the same replacement same multiple times.
|
||||||
|
// Also do the replacement comparison and creation in a single synchronized block.
|
||||||
synchronized (videoIdLockObject) {
|
synchronized (videoIdLockObject) {
|
||||||
if (replacementLikeDislikeSpan != null) {
|
if (originalDislikeSpan != null && replacementLikeDislikeSpan != null) {
|
||||||
if (spansHaveEqualTextAndColor(replacementLikeDislikeSpan, oldSpannable)) {
|
if (spansHaveEqualTextAndColor(oldSpannable, replacementLikeDislikeSpan)) {
|
||||||
LogHelper.printDebug(() -> "Ignoring previously created dislikes span");
|
LogHelper.printDebug(() -> "Ignoring previously created dislikes span");
|
||||||
return oldSpannable;
|
return oldSpannable;
|
||||||
}
|
}
|
||||||
if (spansHaveEqualTextAndColor(Objects.requireNonNull(originalDislikeSpan), oldSpannable)) {
|
if (spansHaveEqualTextAndColor(oldSpannable, originalDislikeSpan)) {
|
||||||
LogHelper.printDebug(() -> "Replacing span with previously created dislike span");
|
LogHelper.printDebug(() -> "Replacing span with previously created dislike span");
|
||||||
return replacementLikeDislikeSpan;
|
return replacementLikeDislikeSpan;
|
||||||
}
|
}
|
||||||
@ -258,31 +335,19 @@ public class ReturnYouTubeDislike {
|
|||||||
return oldSpannable;
|
return oldSpannable;
|
||||||
}
|
}
|
||||||
oldSpannable = originalDislikeSpan;
|
oldSpannable = originalDislikeSpan;
|
||||||
} else {
|
|
||||||
originalDislikeSpan = oldSpannable; // most up to date original
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Must block the current thread until fetching is done.
|
// No replacement span exist, create it now.
|
||||||
// There's no known way to edit the text after creation yet.
|
|
||||||
Future<RYDVoteData> fetchFuture = getVoteFetchFuture();
|
|
||||||
if (fetchFuture == null) {
|
|
||||||
LogHelper.printDebug(() -> "fetch future not available (user enabled RYD while video was playing?)");
|
|
||||||
return oldSpannable;
|
|
||||||
}
|
|
||||||
RYDVoteData votingData = fetchFuture.get(MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS);
|
|
||||||
if (votingData == null) {
|
|
||||||
LogHelper.printDebug(() -> "Cannot add dislike to UI (RYD data not available)");
|
|
||||||
return oldSpannable;
|
|
||||||
}
|
|
||||||
|
|
||||||
SpannableString replacement = createDislikeSpan(oldSpannable, isSegmentedButton, votingData);
|
if (userVote != null) {
|
||||||
synchronized (videoIdLockObject) {
|
votingData.updateUsingVote(userVote);
|
||||||
replacementLikeDislikeSpan = replacement;
|
}
|
||||||
|
originalDislikeSpan = oldSpannable;
|
||||||
|
replacementLikeDislikeSpan = createDislikeSpan(oldSpannable, isSegmentedButton, votingData);
|
||||||
|
LogHelper.printDebug(() -> "Replaced: '" + originalDislikeSpan + "' with: '" + replacementLikeDislikeSpan + "'");
|
||||||
|
|
||||||
|
return replacementLikeDislikeSpan;
|
||||||
}
|
}
|
||||||
final Spanned oldSpannableLogging = oldSpannable;
|
|
||||||
LogHelper.printDebug(() -> "Replaced: '" + oldSpannableLogging + "' with: '" + replacement + "'");
|
|
||||||
return replacement;
|
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
LogHelper.printDebug(() -> "UI timed out while waiting for fetch votes to complete"); // show no toast
|
LogHelper.printDebug(() -> "UI timed out while waiting for fetch votes to complete"); // show no toast
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -291,13 +356,22 @@ public class ReturnYouTubeDislike {
|
|||||||
return oldSpannable;
|
return oldSpannable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if the RYD fetch call has completed.
|
||||||
|
*/
|
||||||
|
public static boolean fetchCompleted() {
|
||||||
|
Future<RYDVoteData> future = getVoteFetchFuture();
|
||||||
|
return future != null && future.isDone();
|
||||||
|
}
|
||||||
|
|
||||||
public static void sendVote(@NonNull Vote vote) {
|
public static void sendVote(@NonNull Vote vote) {
|
||||||
ReVancedUtils.verifyOnMainThread();
|
ReVancedUtils.verifyOnMainThread();
|
||||||
Objects.requireNonNull(vote);
|
Objects.requireNonNull(vote);
|
||||||
try {
|
try {
|
||||||
// Must make a local copy of videoId, since it may change between now and when the vote thread runs.
|
// Must make a local copy of videoId, since it may change between now and when the vote thread runs.
|
||||||
String videoIdToVoteFor = getCurrentVideoId();
|
String videoIdToVoteFor = getCurrentVideoId();
|
||||||
if (videoIdToVoteFor == null || dislikeDataIsShort != PlayerType.getCurrent().isNoneOrHidden()) {
|
if (videoIdToVoteFor == null ||
|
||||||
|
(SettingsEnum.RYD_SHORTS.getBoolean() && dislikeDataIsShort != PlayerType.getCurrent().isNoneHiddenOrMinimized())) {
|
||||||
// User enabled RYD after starting playback of a video.
|
// User enabled RYD after starting playback of a video.
|
||||||
// Or shorts was loaded with regular video present, then shorts was closed,
|
// Or shorts was loaded with regular video present, then shorts was closed,
|
||||||
// and then user voted on the now visible original video.
|
// and then user voted on the now visible original video.
|
||||||
@ -317,27 +391,48 @@ public class ReturnYouTubeDislike {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
clearCache(); // UI needs updating
|
setUserVote(vote);
|
||||||
|
|
||||||
// Update the downloaded vote data.
|
|
||||||
Future<RYDVoteData> future = getVoteFetchFuture();
|
|
||||||
if (future == null) {
|
|
||||||
LogHelper.printException(() -> "Cannot update UI dislike count - vote fetch is null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// The future should always be completed before user can like/dislike, but use a timeout just in case.
|
|
||||||
RYDVoteData voteData = future.get(MAX_MILLISECONDS_TO_BLOCK_UI_WHILE_WAITING_FOR_FETCH_VOTES_TO_COMPLETE, TimeUnit.MILLISECONDS);
|
|
||||||
if (voteData == null) {
|
|
||||||
// RYD fetch failed
|
|
||||||
LogHelper.printDebug(() -> "Cannot update UI (vote data not available)");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
voteData.updateUsingVote(vote);
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Error trying to send vote", ex);
|
LogHelper.printException(() -> "Error trying to send vote", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setUserVote(@NonNull Vote vote) {
|
||||||
|
Objects.requireNonNull(vote);
|
||||||
|
try {
|
||||||
|
LogHelper.printDebug(() -> "setUserVote: " + vote);
|
||||||
|
|
||||||
|
// Update the downloaded vote data.
|
||||||
|
Future<RYDVoteData> future = getVoteFetchFuture();
|
||||||
|
if (future != null && future.isDone()) {
|
||||||
|
RYDVoteData voteData;
|
||||||
|
try {
|
||||||
|
voteData = future.get(MAX_MILLISECONDS_TO_BLOCK_UI_WAITING_FOR_FETCH, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (ExecutionException | InterruptedException | TimeoutException ex) {
|
||||||
|
// Should never happen
|
||||||
|
LogHelper.printInfo(() -> "Could not update vote data", ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (voteData == null) {
|
||||||
|
// RYD fetch failed
|
||||||
|
LogHelper.printDebug(() -> "Cannot update UI (vote data not available)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
voteData.updateUsingVote(vote);
|
||||||
|
} // Else, vote will be applied after vote data is received
|
||||||
|
|
||||||
|
synchronized (videoIdLockObject) {
|
||||||
|
if (userVote != vote) {
|
||||||
|
userVote = vote;
|
||||||
|
clearCache(); // UI needs updating
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "setUserVote failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Must call off main thread, as this will make a network call if user is not yet registered.
|
* Must call off main thread, as this will make a network call if user is not yet registered.
|
||||||
*
|
*
|
||||||
@ -363,6 +458,7 @@ public class ReturnYouTubeDislike {
|
|||||||
/**
|
/**
|
||||||
* @param isSegmentedButton If UI is using the segmented single UI component for both like and dislike.
|
* @param isSegmentedButton If UI is using the segmented single UI component for both like and dislike.
|
||||||
*/
|
*/
|
||||||
|
@NonNull
|
||||||
private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, boolean isSegmentedButton, @NonNull RYDVoteData voteData) {
|
private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, boolean isSegmentedButton, @NonNull RYDVoteData voteData) {
|
||||||
if (!isSegmentedButton) {
|
if (!isSegmentedButton) {
|
||||||
// Simple replacement of 'dislike' with a number/percentage.
|
// Simple replacement of 'dislike' with a number/percentage.
|
||||||
@ -393,7 +489,7 @@ public class ReturnYouTubeDislike {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SpannableStringBuilder builder = new SpannableStringBuilder();
|
SpannableStringBuilder builder = new SpannableStringBuilder();
|
||||||
final boolean compactLayout = SettingsEnum.RYD_USE_COMPACT_LAYOUT.getBoolean();
|
final boolean compactLayout = SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean();
|
||||||
final int separatorColor = ThemeHelper.isDarkTheme()
|
final int separatorColor = ThemeHelper.isDarkTheme()
|
||||||
? 0x29AAAAAA // transparent dark gray
|
? 0x29AAAAAA // transparent dark gray
|
||||||
: 0xFFD9D9D9; // light gray
|
: 0xFFD9D9D9; // light gray
|
||||||
@ -477,12 +573,12 @@ public class ReturnYouTubeDislike {
|
|||||||
|
|
||||||
private static SpannableString newSpannableWithDislikes(@NonNull Spanned sourceStyling, @NonNull RYDVoteData voteData) {
|
private static SpannableString newSpannableWithDislikes(@NonNull Spanned sourceStyling, @NonNull RYDVoteData voteData) {
|
||||||
return newSpanUsingStylingOfAnotherSpan(sourceStyling,
|
return newSpanUsingStylingOfAnotherSpan(sourceStyling,
|
||||||
SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean()
|
SettingsEnum.RYD_DISLIKE_PERCENTAGE.getBoolean()
|
||||||
? formatDislikePercentage(voteData.getDislikePercentage())
|
? formatDislikePercentage(voteData.getDislikePercentage())
|
||||||
: formatDislikeCount(voteData.getDislikeCount()));
|
: formatDislikeCount(voteData.getDislikeCount()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SpannableString newSpanUsingStylingOfAnotherSpan(@NonNull Spanned sourceStyle, @NonNull String newSpanText) {
|
private static SpannableString newSpanUsingStylingOfAnotherSpan(@NonNull Spanned sourceStyle, @NonNull CharSequence newSpanText) {
|
||||||
SpannableString destination = new SpannableString(newSpanText);
|
SpannableString destination = new SpannableString(newSpanText);
|
||||||
Object[] spans = sourceStyle.getSpans(0, sourceStyle.length(), Object.class);
|
Object[] spans = sourceStyle.getSpans(0, sourceStyle.length(), Object.class);
|
||||||
for (Object span : spans) {
|
for (Object span : spans) {
|
||||||
|
@ -82,15 +82,12 @@ public final class RYDVoteData {
|
|||||||
|
|
||||||
public void updateUsingVote(Vote vote) {
|
public void updateUsingVote(Vote vote) {
|
||||||
if (vote == Vote.LIKE) {
|
if (vote == Vote.LIKE) {
|
||||||
LogHelper.printDebug(() -> "Increasing like count");
|
|
||||||
likeCount = fetchedLikeCount + 1;
|
likeCount = fetchedLikeCount + 1;
|
||||||
dislikeCount = fetchedDislikeCount;
|
dislikeCount = fetchedDislikeCount;
|
||||||
} else if (vote == Vote.DISLIKE) {
|
} else if (vote == Vote.DISLIKE) {
|
||||||
LogHelper.printDebug(() -> "Increasing dislike count");
|
|
||||||
likeCount = fetchedLikeCount;
|
likeCount = fetchedLikeCount;
|
||||||
dislikeCount = fetchedDislikeCount + 1;
|
dislikeCount = fetchedDislikeCount + 1;
|
||||||
} else if (vote == Vote.LIKE_REMOVE) {
|
} else if (vote == Vote.LIKE_REMOVE) {
|
||||||
LogHelper.printDebug(() -> "Resetting like/dislike to fetched values");
|
|
||||||
likeCount = fetchedLikeCount;
|
likeCount = fetchedLikeCount;
|
||||||
dislikeCount = fetchedDislikeCount;
|
dislikeCount = fetchedDislikeCount;
|
||||||
} else {
|
} else {
|
||||||
|
@ -5,11 +5,13 @@ import static app.revanced.integrations.utils.StringRef.str;
|
|||||||
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.ProtocolException;
|
import java.net.ProtocolException;
|
||||||
@ -22,6 +24,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import app.revanced.integrations.requests.Requester;
|
import app.revanced.integrations.requests.Requester;
|
||||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
@ -219,6 +222,15 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) {
|
||||||
|
if (SettingsEnum.RYD_TOAST_ON_CONNECTION_ERROR.getBoolean()) {
|
||||||
|
ReVancedUtils.showToastShort(toastMessage);
|
||||||
|
}
|
||||||
|
if (ex != null) {
|
||||||
|
LogHelper.printInfo(() -> toastMessage, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return NULL if fetch failed, or if a rate limit is in effect.
|
* @return NULL if fetch failed, or if a rate limit is in effect.
|
||||||
*/
|
*/
|
||||||
@ -272,12 +284,13 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
LogHelper.printDebug(() -> "Video has no like/dislikes (video is a YouTube Story?): " + videoId);
|
LogHelper.printDebug(() -> "Video has no like/dislikes (video is a YouTube Story?): " + videoId);
|
||||||
return null; // do not updated connection statistics
|
return null; // do not updated connection statistics
|
||||||
} else {
|
} else {
|
||||||
LogHelper.printException(() -> "Failed to fetch votes for video: " + videoId + " response code was: " + responseCode,
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||||
null, str("revanced_ryd_failure_connection_status_code", responseCode));
|
|
||||||
connection.disconnect(); // something went wrong, might as well disconnect
|
|
||||||
}
|
}
|
||||||
|
connection.disconnect(); // something went wrong, might as well disconnect
|
||||||
} catch (SocketTimeoutException ex) { // connection timed out, response timeout, or some other network error
|
} catch (SocketTimeoutException ex) { // connection timed out, response timeout, or some other network error
|
||||||
LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_connection_timeout"));
|
handleConnectionError((str("revanced_ryd_failure_connection_timeout")), ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
handleConnectionError((str("revanced_ryd_failure_generic", ex.getMessage())), ex);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
// should never happen
|
// should never happen
|
||||||
LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_generic", ex.getMessage()));
|
LogHelper.printException(() -> "Failed to fetch votes", ex, str("revanced_ryd_failure_generic", ex.getMessage()));
|
||||||
@ -318,11 +331,14 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
String solution = solvePuzzle(challenge, difficulty);
|
String solution = solvePuzzle(challenge, difficulty);
|
||||||
return confirmRegistration(userId, solution);
|
return confirmRegistration(userId, solution);
|
||||||
}
|
}
|
||||||
LogHelper.printException(() -> "Failed to register new user: " + userId
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||||
+ " response code was: " + responseCode); // failed attempt, and ok to log userId
|
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
|
} catch (SocketTimeoutException ex) {
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_generic", "registration failed"), ex);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Failed to register user", ex);
|
LogHelper.printException(() -> "Failed to register user", ex); // should never happen
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -351,19 +367,23 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
String result = null;
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
String result = Requester.parseJson(connection);
|
result = Requester.parseJson(connection);
|
||||||
if (result.equalsIgnoreCase("true")) {
|
if (result.equalsIgnoreCase("true")) {
|
||||||
LogHelper.printDebug(() -> "Registration confirmation successful");
|
LogHelper.printDebug(() -> "Registration confirmation successful");
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
LogHelper.printException(() -> "Failed to confirm registration for user: " + userId
|
|
||||||
+ " solution: " + solution + " response string was: " + result);
|
|
||||||
} else {
|
|
||||||
LogHelper.printException(() -> "Failed to confirm registration for user: " + userId
|
|
||||||
+ " solution: " + solution + " response code was: " + responseCode);
|
|
||||||
}
|
}
|
||||||
|
final String resultLog = result == null ? "(no response)" : result;
|
||||||
|
LogHelper.printInfo(() -> "Failed to confirm registration for user: " + userId
|
||||||
|
+ " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog);
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||||
connection.disconnect(); // something went wrong, might as well disconnect
|
connection.disconnect(); // something went wrong, might as well disconnect
|
||||||
|
} catch (SocketTimeoutException ex) {
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_generic", "confirm registration failed"), ex);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Failed to confirm registration for user: " + userId
|
LogHelper.printException(() -> "Failed to confirm registration for user: " + userId
|
||||||
+ "solution: " + solution, ex);
|
+ "solution: " + solution, ex);
|
||||||
@ -405,10 +425,16 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
String solution = solvePuzzle(challenge, difficulty);
|
String solution = solvePuzzle(challenge, difficulty);
|
||||||
return confirmVote(videoId, userId, solution);
|
return confirmVote(videoId, userId, solution);
|
||||||
}
|
}
|
||||||
LogHelper.printException(() -> "Failed to send vote for video: " + videoId
|
LogHelper.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote
|
||||||
+ " vote: " + vote + " response code was: " + responseCode);
|
+ " response code was: " + responseCode);
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||||
connection.disconnect(); // something went wrong, might as well disconnect
|
connection.disconnect(); // something went wrong, might as well disconnect
|
||||||
|
} catch (SocketTimeoutException ex) {
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_generic", "send vote failed"), ex);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
// should never happen
|
||||||
LogHelper.printException(() -> "Failed to send vote for video: " + videoId + " vote: " + vote, ex);
|
LogHelper.printException(() -> "Failed to send vote for video: " + videoId + " vote: " + vote, ex);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -438,23 +464,26 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
String result = null;
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
String result = Requester.parseJson(connection);
|
result = Requester.parseJson(connection);
|
||||||
if (result.equalsIgnoreCase("true")) {
|
if (result.equalsIgnoreCase("true")) {
|
||||||
LogHelper.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
LogHelper.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
|
|
||||||
+ " solution: " + solution + " response string was: " + result);
|
|
||||||
} else {
|
|
||||||
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
|
|
||||||
+ " solution: " + solution + " response code was: " + responseCode);
|
|
||||||
}
|
}
|
||||||
|
final String resultLog = result == null ? "(no response)" : result;
|
||||||
|
LogHelper.printInfo(() -> "Failed to confirm vote for video: " + videoId
|
||||||
|
+ " solution: " + solution + " responseCode: " + responseCode + " responseString: " + resultLog);
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||||
connection.disconnect(); // something went wrong, might as well disconnect
|
connection.disconnect(); // something went wrong, might as well disconnect
|
||||||
|
} catch (SocketTimeoutException ex) {
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_connection_timeout"), ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
handleConnectionError(str("revanced_ryd_failure_generic", "confirm vote failed"), ex);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
|
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
|
||||||
+ " solution: " + solution, ex);
|
+ " solution: " + solution, ex); // should never happen
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -503,7 +532,7 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// should never be reached
|
// should never be reached
|
||||||
throw new IllegalStateException("Failed to solve puzzle challenge: " + challenge + " of difficulty: " + difficulty);
|
throw new IllegalStateException("Failed to solve puzzle challenge: " + challenge + " difficulty: " + difficulty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackoverflow.com/a/157202
|
// https://stackoverflow.com/a/157202
|
||||||
@ -519,9 +548,8 @@ public class ReturnYouTubeDislikeApi {
|
|||||||
|
|
||||||
private static int countLeadingZeroes(byte[] uInt8View) {
|
private static int countLeadingZeroes(byte[] uInt8View) {
|
||||||
int zeroes = 0;
|
int zeroes = 0;
|
||||||
int value;
|
|
||||||
for (byte b : uInt8View) {
|
for (byte b : uInt8View) {
|
||||||
value = b & 0xFF;
|
int value = b & 0xFF;
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
zeroes += 8;
|
zeroes += 8;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,69 +1,90 @@
|
|||||||
package app.revanced.integrations.settings;
|
package app.revanced.integrations.settings;
|
||||||
|
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.BOOLEAN;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.FLOAT;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.INTEGER;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.LONG;
|
||||||
|
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.STRING;
|
||||||
|
import static app.revanced.integrations.settings.SharedPrefCategory.RETURN_YOUTUBE_DISLIKE;
|
||||||
|
import static app.revanced.integrations.settings.SharedPrefCategory.SPONSOR_BLOCK;
|
||||||
|
import static app.revanced.integrations.utils.StringRef.str;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import app.revanced.integrations.utils.StringRef;
|
|
||||||
import app.revanced.integrations.patches.theme.ThemePatch;
|
|
||||||
|
|
||||||
|
import app.revanced.integrations.sponsorblock.SponsorBlockSettings;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
import app.revanced.integrations.utils.StringRef;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static app.revanced.integrations.settings.SettingsEnum.ReturnType.*;
|
|
||||||
import static app.revanced.integrations.settings.SharedPrefCategory.RETURN_YOUTUBE_DISLIKE;
|
|
||||||
import static app.revanced.integrations.settings.SharedPrefCategory.SPONSOR_BLOCK;
|
|
||||||
import static java.lang.Boolean.FALSE;
|
|
||||||
import static java.lang.Boolean.TRUE;
|
|
||||||
|
|
||||||
public enum SettingsEnum {
|
public enum SettingsEnum {
|
||||||
//Download Settings
|
// External downloader
|
||||||
DOWNLOADS_BUTTON_SHOWN("revanced_downloads_enabled", BOOLEAN, TRUE),
|
EXTERNAL_DOWNLOADER("revanced_external_downloader", BOOLEAN, TRUE),
|
||||||
DOWNLOADS_PACKAGE_NAME("revanced_downloads_package_name", STRING, "org.schabi.newpipe" /* NewPipe */, parents(DOWNLOADS_BUTTON_SHOWN)),
|
EXTERNAL_DOWNLOADER_PACKAGE_NAME("revanced_external_downloader_name", STRING,
|
||||||
|
"org.schabi.newpipe" /* NewPipe */, parents(EXTERNAL_DOWNLOADER)),
|
||||||
|
|
||||||
// Copy video URL settings
|
// Copy video URL
|
||||||
COPY_VIDEO_URL_BUTTON_SHOWN("revanced_copy_video_url_enabled", BOOLEAN, TRUE),
|
COPY_VIDEO_URL("revanced_copy_video_url", BOOLEAN, FALSE),
|
||||||
COPY_VIDEO_URL_TIMESTAMP_BUTTON_SHOWN("revanced_copy_video_url_timestamp_enabled", BOOLEAN, TRUE),
|
COPY_VIDEO_URL_TIMESTAMP("revanced_copy_video_url_timestamp", BOOLEAN, TRUE),
|
||||||
|
|
||||||
// Video settings
|
// Video
|
||||||
OLD_STYLE_VIDEO_QUALITY_PLAYER_SETTINGS("revanced_use_old_style_quality_settings", BOOLEAN, TRUE),
|
HDR_AUTO_BRIGHTNESS("revanced_hdr_auto_brightness", BOOLEAN, TRUE),
|
||||||
VIDEO_QUALITY_REMEMBER_LAST_SELECTED("revanced_remember_video_quality_last_selected", BOOLEAN, TRUE),
|
SHOW_OLD_VIDEO_MENU("revanced_show_old_video_menu", BOOLEAN, TRUE),
|
||||||
VIDEO_QUALITY_DEFAULT_WIFI("revanced_default_video_quality_wifi", INTEGER, -2),
|
REMEMBER_VIDEO_QUALITY_LAST_SELECTED("revanced_remember_video_quality_last_selected", BOOLEAN, TRUE),
|
||||||
VIDEO_QUALITY_DEFAULT_MOBILE("revanced_default_video_quality_mobile", INTEGER, -2),
|
VIDEO_QUALITY_DEFAULT_WIFI("revanced_video_quality_default_wifi", INTEGER, -2),
|
||||||
PLAYBACK_SPEED_REMEMBER_LAST_SELECTED("revanced_remember_playback_speed_last_selected", BOOLEAN, TRUE),
|
VIDEO_QUALITY_DEFAULT_MOBILE("revanced_video_quality_default_mobile", INTEGER, -2),
|
||||||
PLAYBACK_SPEED_DEFAULT("revanced_default_playback_speed", FLOAT, 1.0f),
|
REMEMBER_PLAYBACK_SPEED_LAST_SELECTED("revanced_remember_playback_speed_last_selected", BOOLEAN, TRUE),
|
||||||
|
PLAYBACK_SPEED_DEFAULT("revanced_playback_speed_default", FLOAT, 1.0f),
|
||||||
|
CUSTOM_PLAYBACK_SPEEDS("revanced_custom_playback_speeds", STRING,
|
||||||
|
"0.25\n0.5\n0.75\n0.9\n0.95\n1.0\n1.05\n1.1\n1.25\n1.5\n1.75\n2.0\n3.0\n4.0\n5.0", true),
|
||||||
|
|
||||||
// TODO: Unused currently
|
// Whitelist
|
||||||
// Whitelist settings
|
//WHITELIST("revanced_whitelist_ads", BOOLEAN, FALSE), // TODO: Unused currently
|
||||||
//ENABLE_WHITELIST("revanced_whitelist_ads_enabled", BOOLEAN, FALSE),
|
|
||||||
|
|
||||||
// Ad settings
|
// Ads
|
||||||
ADREMOVER_BUTTONED_REMOVAL("revanced_adremover_buttoned", BOOLEAN, TRUE),
|
HIDE_BUTTONED_ADS("revanced_hide_buttoned_ads", BOOLEAN, TRUE),
|
||||||
ADREMOVER_CHANNEL_BAR("revanced_hide_channel_bar", BOOLEAN, FALSE),
|
HIDE_GENERAL_ADS("revanced_hide_general_ads", BOOLEAN, TRUE),
|
||||||
ADREMOVER_CHANNEL_MEMBER_SHELF_REMOVAL("revanced_adremover_channel_member_shelf_removal", BOOLEAN, TRUE),
|
HIDE_HIDE_LATEST_POSTS("revanced_hide_latest_posts_ads", BOOLEAN, TRUE),
|
||||||
ADREMOVER_CHAPTER_TEASER_REMOVAL("revanced_adremover_chapter_teaser", BOOLEAN, TRUE),
|
HIDE_PAID_CONTENT("revanced_hide_paid_content_ads", BOOLEAN, TRUE),
|
||||||
ADREMOVER_COMMUNITY_GUIDELINES_REMOVAL("revanced_adremover_community_guidelines", BOOLEAN, TRUE),
|
HIDE_SELF_SPONSOR("revanced_hide_self_sponsor_ads", BOOLEAN, TRUE),
|
||||||
ADREMOVER_COMMUNITY_POSTS_REMOVAL("revanced_adremover_community_posts_removal", BOOLEAN, FALSE),
|
HIDE_VIDEO_ADS("revanced_hide_video_ads", BOOLEAN, TRUE, true),
|
||||||
ADREMOVER_COMPACT_BANNER_REMOVAL("revanced_adremover_compact_banner_removal", BOOLEAN, TRUE),
|
CUSTOM_FILTER("revanced_custom_filter", BOOLEAN, FALSE),
|
||||||
ADREMOVER_CUSTOM_ENABLED("revanced_adremover_custom_enabled", BOOLEAN, FALSE),
|
CUSTOM_FILTER_STRINGS("revanced_custom_filter_strings", STRING, "", true, parents(CUSTOM_FILTER)),
|
||||||
ADREMOVER_CUSTOM_REMOVAL("revanced_adremover_custom_strings", STRING, "", true, parents(ADREMOVER_CUSTOM_ENABLED)),
|
|
||||||
ADREMOVER_EMERGENCY_BOX_REMOVAL("revanced_adremover_emergency_box_removal", BOOLEAN, TRUE),
|
// Layout
|
||||||
ADREMOVER_FEED_SURVEY_REMOVAL("revanced_adremover_feed_survey", BOOLEAN, TRUE),
|
HIDE_CHANNEL_BAR("revanced_hide_channel_bar", BOOLEAN, FALSE),
|
||||||
ADREMOVER_GENERAL_ADS_REMOVAL("revanced_adremover_ad_removal", BOOLEAN, TRUE),
|
HIDE_CHANNEL_MEMBER_SHELF("revanced_hide_channel_member_shelf", BOOLEAN, TRUE),
|
||||||
ADREMOVER_GRAY_SEPARATOR("revanced_adremover_separator", BOOLEAN, TRUE),
|
HIDE_CHAPTER_TEASER("revanced_hide_chapter_teaser", BOOLEAN, TRUE),
|
||||||
ADREMOVER_HIDE_CHANNEL_GUIDELINES("revanced_adremover_hide_channel_guidelines", BOOLEAN, TRUE),
|
HIDE_COMMUNITY_GUIDELINES("revanced_hide_community_guidelines", BOOLEAN, TRUE),
|
||||||
ADREMOVER_HIDE_LATEST_POSTS("revanced_adremover_hide_latest_posts", BOOLEAN, TRUE),
|
HIDE_COMMUNITY_POSTS("revanced_hide_community_posts", BOOLEAN, FALSE),
|
||||||
ADREMOVER_IMAGE_SHELF("revanced_hide_image_shelf", BOOLEAN, TRUE),
|
HIDE_COMPACT_BANNER("revanced_hide_compact_banner", BOOLEAN, TRUE),
|
||||||
ADREMOVER_INFO_PANEL_REMOVAL("revanced_adremover_info_panel", BOOLEAN, TRUE),
|
HIDE_EMERGENCY_BOX("revanced_hide_emergency_box", BOOLEAN, TRUE),
|
||||||
ADREMOVER_MEDICAL_PANEL_REMOVAL("revanced_adremover_medical_panel", BOOLEAN, TRUE),
|
HIDE_FEED_SURVEY("revanced_hide_feed_survey", BOOLEAN, TRUE),
|
||||||
ADREMOVER_MERCHANDISE_REMOVAL("revanced_adremover_merchandise", BOOLEAN, TRUE),
|
HIDE_GRAY_SEPARATOR("revanced_hide_gray_separator", BOOLEAN, TRUE),
|
||||||
ADREMOVER_MOVIE_REMOVAL("revanced_adremover_movie", BOOLEAN, TRUE),
|
HIDE_HIDE_CHANNEL_GUIDELINES("revanced_hide_channel_guidelines", BOOLEAN, TRUE),
|
||||||
ADREMOVER_PAID_CONTENT_REMOVAL("revanced_adremover_paid_content", BOOLEAN, TRUE),
|
HIDE_IMAGE_SHELF("revanced_hide_image_shelf", BOOLEAN, TRUE),
|
||||||
ADREMOVER_QUICK_ACTIONS("revanced_hide_quick_actions", BOOLEAN, FALSE),
|
HIDE_HIDE_INFO_PANELS("revanced_hide_info_panels", BOOLEAN, TRUE),
|
||||||
ADREMOVER_RELATED_VIDEOS("revanced_hide_related_videos", BOOLEAN, FALSE),
|
HIDE_MEDICAL_PANELS("revanced_hide_medical_panels", BOOLEAN, TRUE),
|
||||||
ADREMOVER_SELF_SPONSOR_REMOVAL("revanced_adremover_self_sponsor", BOOLEAN, TRUE),
|
HIDE_MERCHANDISE_BANNERS("revanced_hide_merchandise_banners", BOOLEAN, TRUE),
|
||||||
ADREMOVER_SHORTS_REMOVAL("revanced_adremover_shorts", BOOLEAN, TRUE, true),
|
HIDE_MOVIES_SECTION("revanced_hide_movies_section", BOOLEAN, TRUE),
|
||||||
ADREMOVER_SUBSCRIBERS_COMMUNITY_GUIDELINES_REMOVAL("revanced_adremover_subscribers_community_guidelines_removal", BOOLEAN, TRUE),
|
HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES("revanced_hide_subscribers_community_guidelines", BOOLEAN, TRUE),
|
||||||
ADREMOVER_VIEW_PRODUCTS("revanced_adremover_view_products", BOOLEAN, TRUE),
|
HIDE_PRODUCTS_BANNER("revanced_hide_products_banner", BOOLEAN, TRUE),
|
||||||
ADREMOVER_WEB_SEARCH_RESULTS("revanced_adremover_web_search_result", BOOLEAN, TRUE),
|
HIDE_WEB_SEARCH_RESULTS("revanced_hide_web_search_results", BOOLEAN, TRUE),
|
||||||
VIDEO_ADS_REMOVAL("revanced_video_ads_removal", BOOLEAN, TRUE, true),
|
HIDE_QUICK_ACTIONS("revanced_hide_quick_actions", BOOLEAN, FALSE),
|
||||||
|
HIDE_RELATED_VIDEOS("revanced_hide_related_videos", BOOLEAN, FALSE),
|
||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
HIDE_LIKE_DISLIKE_BUTTON("revanced_hide_like_dislike_button", BOOLEAN, FALSE),
|
HIDE_LIKE_DISLIKE_BUTTON("revanced_hide_like_dislike_button", BOOLEAN, FALSE),
|
||||||
@ -72,8 +93,8 @@ public enum SettingsEnum {
|
|||||||
HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, FALSE, "revanced_hide_clip_button_user_dialog_message"),
|
HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, FALSE, "revanced_hide_clip_button_user_dialog_message"),
|
||||||
HIDE_ACTION_BUTTONS("revanced_hide_action_buttons", BOOLEAN, FALSE),
|
HIDE_ACTION_BUTTONS("revanced_hide_action_buttons", BOOLEAN, FALSE),
|
||||||
|
|
||||||
// Layout settings
|
// Layout
|
||||||
DISABLE_STARTUP_SHORTS_PLAYER("revanced_startup_shorts_player_enabled", BOOLEAN, FALSE),
|
DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE),
|
||||||
HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true),
|
HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true),
|
||||||
HIDE_ARTIST_CARDS("revanced_hide_artist_cards", BOOLEAN, FALSE),
|
HIDE_ARTIST_CARDS("revanced_hide_artist_cards", BOOLEAN, FALSE),
|
||||||
HIDE_AUDIO_TRACK_BUTTON("revanced_hide_audio_track_button", BOOLEAN, FALSE),
|
HIDE_AUDIO_TRACK_BUTTON("revanced_hide_audio_track_button", BOOLEAN, FALSE),
|
||||||
@ -83,87 +104,253 @@ public enum SettingsEnum {
|
|||||||
HIDE_CAST_BUTTON("revanced_hide_cast_button", BOOLEAN, TRUE, true),
|
HIDE_CAST_BUTTON("revanced_hide_cast_button", BOOLEAN, TRUE, true),
|
||||||
HIDE_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, true),
|
HIDE_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, true),
|
||||||
HIDE_CREATE_BUTTON("revanced_hide_create_button", BOOLEAN, TRUE, true),
|
HIDE_CREATE_BUTTON("revanced_hide_create_button", BOOLEAN, TRUE, true),
|
||||||
SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_CROWDFUNDING_BOX("revanced_hide_crowdfunding_box", BOOLEAN, FALSE, true),
|
HIDE_CROWDFUNDING_BOX("revanced_hide_crowdfunding_box", BOOLEAN, FALSE, true),
|
||||||
HIDE_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE),
|
HIDE_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE),
|
||||||
HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE),
|
HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE),
|
||||||
HIDE_FLOATING_MICROPHONE_BUTTON("revanced_hide_floating_microphone_button", BOOLEAN, TRUE, true),
|
HIDE_FLOATING_MICROPHONE_BUTTON("revanced_hide_floating_microphone_button", BOOLEAN, TRUE, true),
|
||||||
HIDE_FULLSCREEN_PANELS("revanced_hide_fullscreen_panels", BOOLEAN, TRUE),
|
HIDE_FULLSCREEN_PANELS("revanced_hide_fullscreen_panels", BOOLEAN, TRUE),
|
||||||
HIDE_GET_PREMIUM("revanced_hide_get_premium", BOOLEAN, TRUE),
|
HIDE_GET_PREMIUM("revanced_hide_get_premium", BOOLEAN, TRUE),
|
||||||
HIDE_INFO_CARDS("revanced_hide_infocards", BOOLEAN, TRUE),
|
HIDE_INFO_CARDS("revanced_hide_info_cards", BOOLEAN, TRUE),
|
||||||
|
HIDE_LOAD_MORE_BUTTON("revanced_hide_load_more_button", BOOLEAN, TRUE, true),
|
||||||
HIDE_PLAYER_BUTTONS("revanced_hide_player_buttons", BOOLEAN, FALSE),
|
HIDE_PLAYER_BUTTONS("revanced_hide_player_buttons", BOOLEAN, FALSE),
|
||||||
|
HIDE_PLAYER_OVERLAY("revanced_hide_player_overlay", BOOLEAN, FALSE, true),
|
||||||
HIDE_PREVIEW_COMMENT("revanced_hide_preview_comment", BOOLEAN, FALSE, true),
|
HIDE_PREVIEW_COMMENT("revanced_hide_preview_comment", BOOLEAN, FALSE, true),
|
||||||
HIDE_SEEKBAR("revanced_hide_seekbar", BOOLEAN, FALSE),
|
HIDE_SEEKBAR("revanced_hide_seekbar", BOOLEAN, FALSE, true),
|
||||||
HIDE_HOME_BUTTON("revanced_hide_home_button", BOOLEAN, FALSE, true),
|
HIDE_HOME_BUTTON("revanced_hide_home_button", BOOLEAN, FALSE, true),
|
||||||
HIDE_SHORTS_BUTTON("revanced_hide_shorts_button", BOOLEAN, TRUE, true),
|
HIDE_SHORTS_BUTTON("revanced_hide_shorts_button", BOOLEAN, TRUE, true),
|
||||||
HIDE_SUBSCRIPTIONS_BUTTON("revanced_hide_subscriptions_button", BOOLEAN, FALSE, true),
|
HIDE_SUBSCRIPTIONS_BUTTON("revanced_hide_subscriptions_button", BOOLEAN, FALSE, true),
|
||||||
HIDE_SHORTS_COMMENTS_BUTTON("revanced_hide_shorts_comments_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_TIMESTAMP("revanced_hide_timestamp", BOOLEAN, FALSE),
|
HIDE_TIMESTAMP("revanced_hide_timestamp", BOOLEAN, FALSE),
|
||||||
HIDE_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, TRUE),
|
HIDE_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, TRUE),
|
||||||
HIDE_WATCH_IN_VR("revanced_hide_watch_in_vr", BOOLEAN, FALSE, true),
|
HIDE_WATCH_IN_VR("revanced_hide_watch_in_vr", BOOLEAN, FALSE, true),
|
||||||
PLAYER_POPUP_PANELS("revanced_player_popup_panels_enabled", BOOLEAN, FALSE),
|
PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE),
|
||||||
|
SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true),
|
||||||
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
|
SPOOF_APP_VERSION("revanced_spoof_app_version", BOOLEAN, FALSE, true, "revanced_spoof_app_version_user_dialog_message"),
|
||||||
SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.30.35", true, parents(SPOOF_APP_VERSION)),
|
SPOOF_APP_VERSION_TARGET("revanced_spoof_app_version_target", STRING, "17.30.35", true, parents(SPOOF_APP_VERSION)),
|
||||||
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
|
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
|
||||||
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
|
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
|
||||||
SEEKBAR_COLOR("revanced_seekbar_color", STRING, Integer.toHexString(ThemePatch.DEFAULT_SEEKBAR_COLOR), true),
|
SEEKBAR_COLOR("revanced_seekbar_color", STRING, "#FF0000", true),
|
||||||
|
HIDE_FILTER_BAR_FEED_IN_FEED("revanced_hide_filter_bar_feed_in_feed", BOOLEAN, FALSE, true),
|
||||||
|
HIDE_FILTER_BAR_FEED_IN_SEARCH("revanced_hide_filter_bar_feed_in_search", BOOLEAN, FALSE, true),
|
||||||
|
HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS("revanced_hide_filter_bar_feed_in_related_videos", BOOLEAN, FALSE, true),
|
||||||
|
HIDE_SHORTS_JOIN_BUTTON("revanced_hide_shorts_join_button", BOOLEAN, FALSE),
|
||||||
|
HIDE_SHORTS_SUBSCRIBE_BUTTON("revanced_hide_shorts_subscribe_button", BOOLEAN, FALSE),
|
||||||
|
HIDE_SHORTS_THANKS_BUTTON("revanced_hide_shorts_thanks_button", BOOLEAN, FALSE),
|
||||||
|
HIDE_SHORTS_COMMENTS_BUTTON("revanced_hide_shorts_comments_button", BOOLEAN, FALSE),
|
||||||
|
HIDE_SHORTS_REMIX_BUTTON("revanced_hide_shorts_remix_button", BOOLEAN, FALSE),
|
||||||
|
HIDE_SHORTS_SHARE_BUTTON("revanced_hide_shorts_share_button", BOOLEAN, FALSE),
|
||||||
|
HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true),
|
||||||
|
HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true),
|
||||||
|
|
||||||
// Misc. Settings
|
// Misc
|
||||||
SIGNATURE_SPOOFING("revanced_spoof_signature_verification", BOOLEAN, TRUE, "revanced_spoof_signature_verification_user_dialog_message"),
|
AUTO_CAPTIONS("revanced_auto_captions", BOOLEAN, FALSE),
|
||||||
CAPTIONS_ENABLED("revanced_autocaptions_enabled", BOOLEAN, FALSE),
|
|
||||||
DISABLE_ZOOM_HAPTICS("revanced_disable_zoom_haptics", BOOLEAN, TRUE),
|
DISABLE_ZOOM_HAPTICS("revanced_disable_zoom_haptics", BOOLEAN, TRUE),
|
||||||
ENABLE_EXTERNAL_BROWSER("revanced_enable_external_browser", BOOLEAN, TRUE, true),
|
EXTERNAL_BROWSER("revanced_external_browser", BOOLEAN, TRUE, true),
|
||||||
PREFERRED_AUTO_REPEAT("revanced_pref_auto_repeat", BOOLEAN, FALSE),
|
AUTO_REPEAT("revanced_auto_repeat", BOOLEAN, FALSE),
|
||||||
TAP_SEEKING_ENABLED("revanced_enable_tap_seeking", BOOLEAN, TRUE),
|
SEEKBAR_TAPPING("revanced_seekbar_tapping", BOOLEAN, TRUE),
|
||||||
USE_HDR_AUTO_BRIGHTNESS("revanced_pref_hdr_autobrightness", BOOLEAN, TRUE),
|
SPOOF_SIGNATURE_VERIFICATION("revanced_spoof_signature_verification", BOOLEAN, TRUE, "revanced_spoof_signature_verification_user_dialog_message"),
|
||||||
|
|
||||||
// Swipe controls
|
// Swipe controls
|
||||||
ENABLE_SWIPE_BRIGHTNESS("revanced_enable_swipe_brightness", BOOLEAN, TRUE),
|
SWIPE_BRIGHTNESS("revanced_swipe_brightness", BOOLEAN, TRUE),
|
||||||
ENABLE_SWIPE_VOLUME("revanced_enable_swipe_volume", BOOLEAN, TRUE),
|
SWIPE_VOLUME("revanced_swipe_volume", BOOLEAN, TRUE),
|
||||||
ENABLE_PRESS_TO_SWIPE("revanced_enable_press_to_swipe", BOOLEAN, FALSE,
|
SWIPE_PRESS_TO_ENGAGE("revanced_swipe_press_to_engage", BOOLEAN, FALSE, true,
|
||||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||||
ENABLE_SWIPE_HAPTIC_FEEDBACK("revanced_enable_swipe_haptic_feedback", BOOLEAN, TRUE,
|
SWIPE_HAPTIC_FEEDBACK("revanced_swipe_haptic_feedback", BOOLEAN, TRUE,
|
||||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||||
SWIPE_MAGNITUDE_THRESHOLD("revanced_swipe_magnitude_threshold", FLOAT, 30f,
|
SWIPE_MAGNITUDE_THRESHOLD("revanced_swipe_threshold", INTEGER, 30,
|
||||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||||
SWIPE_OVERLAY_BACKGROUND_ALPHA("revanced_swipe_overlay_background_alpha", INTEGER, 127,
|
SWIPE_OVERLAY_BACKGROUND_ALPHA("revanced_swipe_overlay_background_alpha", INTEGER, 127,
|
||||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||||
SWIPE_OVERLAY_TEXT_SIZE("revanced_swipe_overlay_text_size", FLOAT, 22f,
|
SWIPE_OVERLAY_TEXT_SIZE("revanced_swipe_text_overlay_size", INTEGER, 22,
|
||||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||||
SWIPE_OVERLAY_TIMEOUT("revanced_swipe_overlay_timeout", LONG, 500L,
|
SWIPE_OVERLAY_TIMEOUT("revanced_swipe_overlay_timeout", LONG, 500L,
|
||||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||||
|
|
||||||
// Debug settings
|
// Debugging
|
||||||
DEBUG("revanced_debug_enabled", BOOLEAN, FALSE),
|
DEBUG("revanced_debug", BOOLEAN, FALSE),
|
||||||
DEBUG_STACKTRACE("revanced_debug_stacktrace_enabled", BOOLEAN, FALSE, parents(DEBUG)),
|
DEBUG_STACKTRACE("revanced_debug_stacktrace", BOOLEAN, FALSE, parents(DEBUG)),
|
||||||
DEBUG_SHOW_TOAST_ON_ERROR("revanced_debug_toast_on_error_enabled", BOOLEAN, TRUE, "revanced_debug_toast_on_error_user_dialog_message"),
|
DEBUG_TOAST_ON_ERROR("revanced_debug_toast_on_error", BOOLEAN, TRUE, "revanced_debug_toast_on_error_user_dialog_message"),
|
||||||
|
|
||||||
// ReturnYoutubeDislike settings
|
// ReturnYoutubeDislike
|
||||||
RYD_ENABLED("ryd_enabled", BOOLEAN, TRUE, RETURN_YOUTUBE_DISLIKE),
|
RYD_ENABLED("ryd_enabled", BOOLEAN, TRUE, RETURN_YOUTUBE_DISLIKE),
|
||||||
RYD_USER_ID("ryd_userId", STRING, "", RETURN_YOUTUBE_DISLIKE),
|
RYD_USER_ID("ryd_user_id", STRING, "", RETURN_YOUTUBE_DISLIKE),
|
||||||
RYD_SHOW_DISLIKE_PERCENTAGE("ryd_show_dislike_percentage", BOOLEAN, FALSE, RETURN_YOUTUBE_DISLIKE, parents(RYD_ENABLED)),
|
RYD_SHORTS("ryd_shorts", BOOLEAN, TRUE, RETURN_YOUTUBE_DISLIKE, parents(RYD_ENABLED)),
|
||||||
RYD_USE_COMPACT_LAYOUT("ryd_use_compact_layout", BOOLEAN, FALSE, RETURN_YOUTUBE_DISLIKE, parents(RYD_ENABLED)),
|
RYD_DISLIKE_PERCENTAGE("ryd_dislike_percentage", BOOLEAN, FALSE, RETURN_YOUTUBE_DISLIKE, parents(RYD_ENABLED)),
|
||||||
|
RYD_COMPACT_LAYOUT("ryd_compact_layout", BOOLEAN, FALSE, RETURN_YOUTUBE_DISLIKE, parents(RYD_ENABLED)),
|
||||||
|
RYD_TOAST_ON_CONNECTION_ERROR("ryd_toast_on_connection_error", BOOLEAN, TRUE, RETURN_YOUTUBE_DISLIKE, parents(RYD_ENABLED)),
|
||||||
|
|
||||||
// SponsorBlock settings
|
// SponsorBlock
|
||||||
SB_ENABLED("sb-enabled", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
SB_ENABLED("sb_enabled", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
||||||
SB_VOTING_ENABLED("sb-voting-enabled", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
SB_PRIVATE_USER_ID("sb_private_user_id_Do_Not_Share", STRING, "", SPONSOR_BLOCK), /** Do not use directly, instead use {@link SponsorBlockSettings} */
|
||||||
SB_CREATE_NEW_SEGMENT_ENABLED("sb-new-segment-enabled", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING("uuid", STRING, "", SPONSOR_BLOCK), // Delete sometime in 2024
|
||||||
SB_USE_COMPACT_SKIP_BUTTON("sb-use-compact-skip-button", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
SB_CREATE_NEW_SEGMENT_STEP("sb_create_new_segment_step", INTEGER, 150, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_AUTO_HIDE_SKIP_BUTTON("sb-auto-hide-skip-segment-button", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
SB_VOTING_BUTTON("sb_voting_button", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_SHOW_TOAST_ON_SKIP("show-toast", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
SB_CREATE_NEW_SEGMENT("sb_create_new_segment", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_TRACK_SKIP_COUNT("count-skips", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
SB_COMPACT_SKIP_BUTTON("sb_compact_skip_button", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_UUID("uuid", STRING, "", SPONSOR_BLOCK),
|
SB_AUTO_HIDE_SKIP_BUTTON("sb_auto_hide_skip_button", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_ADJUST_NEW_SEGMENT_STEP("new-segment-step-accuracy", INTEGER, 150, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
SB_TOAST_ON_SKIP("sb_toast_on_skip", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_MIN_DURATION("sb-min-duration", FLOAT, 0F, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
SB_TOAST_ON_CONNECTION_ERROR("sb_toast_on_connection_error", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_SEEN_GUIDELINES("sb-seen-gl", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
SB_TRACK_SKIP_COUNT("sb_track_skip_count", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED("sb-skipped-segments", INTEGER, 0, SPONSOR_BLOCK),
|
SB_SEGMENT_MIN_DURATION("sb_min_segment_duration", FLOAT, 0F, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_SKIPPED_SEGMENTS_TIME_SAVED("sb-skipped-segments-time", LONG, 0L, SPONSOR_BLOCK),
|
SB_VIDEO_LENGTH_WITHOUT_SEGMENTS("sb_video_length_without_segments", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||||
SB_SHOW_TIME_WITHOUT_SEGMENTS("sb-length-without-segments", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
SB_API_URL("sb_api_url", STRING, "https://sponsor.ajay.app", SPONSOR_BLOCK),
|
||||||
SB_IS_VIP("sb-is-vip", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
SB_USER_IS_VIP("sb_user_is_vip", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||||
SB_LAST_VIP_CHECK("sb-last-vip-check", LONG, 0L, SPONSOR_BLOCK),
|
// SB settings not exported
|
||||||
SB_API_URL("sb-api-host-url", STRING, "https://sponsor.ajay.app", SPONSOR_BLOCK);
|
SB_LAST_VIP_CHECK("sb_last_vip_check", LONG, 0L, SPONSOR_BLOCK),
|
||||||
|
SB_HIDE_EXPORT_WARNING("sb_hide_export_warning", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||||
|
SB_SEEN_GUIDELINES("sb_seen_guidelines", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||||
|
SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS("sb_local_time_saved_number_segments", INTEGER, 0, SPONSOR_BLOCK),
|
||||||
|
SB_LOCAL_TIME_SAVED_MILLISECONDS("sb_local_time_saved_milliseconds", LONG, 0L, SPONSOR_BLOCK),
|
||||||
|
|
||||||
private static SettingsEnum[] parents(SettingsEnum ... parents) {
|
//
|
||||||
|
// TODO: eventually, delete these
|
||||||
|
//
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_ADREMOVER_BUTTONED_REMOVAL("revanced_adremover_buttoned", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_ADREMOVER_GENERAL_ADS_REMOVAL("revanced_adremover_ad_removal", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_ADREMOVER_PAID_CONTENT("revanced_adremover_paid_content", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_ADREMOVER_HIDE_LATEST_POSTS("revanced_adremover_hide_latest_posts", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_ADREMOVER_SELF_SPONSOR("revanced_adremover_self_sponsor", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_ADREMOVER_CUSTOM_ENABLED("revanced_adremover_custom_enabled", BOOLEAN, FALSE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_ADREMOVER_CUSTOM_REMOVAL("revanced_adremover_custom_strings", STRING, "", true),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_REMOVE_VIDEO_ADS("revanced_video_ads_removal", BOOLEAN, TRUE, true),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_CHANNEL_MEMBER_SHELF("revanced_adremover_channel_member_shelf_removal", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_CHAPTER_TEASER("revanced_adremover_chapter_teaser", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_COMMUNITY_GUIDELINES("revanced_adremover_community_guidelines", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_COMMUNITY_POSTS("revanced_adremover_community_posts_removal", BOOLEAN, FALSE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_COMPACT_BANNER("revanced_adremover_compact_banner_removal", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_EMERGENCY_BOX("revanced_adremover_emergency_box_removal", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_FEED_SURVEY_REMOVAL("revanced_adremover_feed_survey", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_GRAY_SEPARATOR("revanced_adremover_separator", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_HIDE_CHANNEL_GUIDELINES("revanced_adremover_hide_channel_guidelines", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_INFO_PANEL_REMOVAL("revanced_adremover_info_panel", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_MEDICAL_PANEL_REMOVAL("revanced_adremover_medical_panel", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_MERCHANDISE_REMOVAL("revanced_adremover_merchandise", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_MOVIE_REMOVAL("revanced_adremover_movie", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES_REMOVAL("revanced_adremover_subscribers_community_guidelines_removal", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_VIEW_PRODUCTS("revanced_adremover_view_products", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_WEB_SEARCH_RESULTS("revanced_adremover_web_search_result", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_SHORTS("revanced_adremover_shorts", BOOLEAN, TRUE, true),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HIDE_INFO_CARDS("revanced_hide_infocards", BOOLEAN, TRUE),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_startup_shorts_player", BOOLEAN, FALSE),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_ETERNAL_DOWNLOADER("revanced_downloads_enabled", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_EXTERNAL_DOWNLOADER_PACKAGE_NAME("revanced_downloads_package_name", STRING, "org.schabi.newpipe"),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SHOW_OLD_VIDEO_MENU("revanced_use_old_style_quality_settings", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_VIDEO_QUALITY_DEFAULT_WIFI("revanced_default_video_quality_wifi", INTEGER, -2),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_VIDEO_QUALITY_DEFAULT_MOBILE("revanced_default_video_quality_mobile", INTEGER, -2),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_PLAYBACK_SPEED_DEFAULT("revanced_default_playback_speed", FLOAT, 1.0f),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_COPY_VIDEO_URL("revanced_copy_video_url_enabled", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_COPY_VIDEO_URL_TIMESTAMP("revanced_copy_video_url_timestamp_enabled", BOOLEAN, TRUE),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_AUTO_CAPTIONS("revanced_autocaptions_enabled", BOOLEAN, FALSE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_PLAYER_POPUP_PANELS("revanced_player_popup_panels_enabled", BOOLEAN, FALSE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SWIPE_BRIGHTNESS("revanced_enable_swipe_brightness", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SWIPE_VOLUME("revanced_enable_swipe_volume", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_PRESS_TO_SWIPE("revanced_enable_press_to_swipe", BOOLEAN, FALSE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SWIPE_HAPTIC_FEEDBACK("revanced_enable_swipe_haptic_feedback", BOOLEAN, TRUE),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_DEBUG("revanced_debug_enabled", BOOLEAN, FALSE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_DEBUG_STACKTRACE("revanced_debug_stacktrace_enabled", BOOLEAN, FALSE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_DEBUG_TOAST_ON_ERROR("revanced_debug_toast_on_error_enabled", BOOLEAN, TRUE),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_EXTERNAL_BROWSER("revanced_enable_external_browser", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_AUTO_REPEAT("revanced_pref_auto_repeat", BOOLEAN, FALSE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_TAP_SEEKING("revanced_enable_tap_seeking", BOOLEAN, TRUE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_HDR_AUTO_BRIGHTNESS("revanced_pref_hdr_autobrightness", BOOLEAN, TRUE),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_RYD_USER_ID("ryd_userId", STRING, "", RETURN_YOUTUBE_DISLIKE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_RYD_DISLIKE_PERCENTAGE("ryd_show_dislike_percentage", BOOLEAN, FALSE, RETURN_YOUTUBE_DISLIKE),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_RYD_COMPACT_LAYOUT("ryd_use_compact_layout", BOOLEAN, FALSE, RETURN_YOUTUBE_DISLIKE),
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_ENABLED("sb-enabled", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_VOTING_BUTTON("sb-voting-enabled", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_CREATE_NEW_SEGMENT("sb-new-segment-enabled", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_COMPACT_SKIP_BUTTON("sb-use-compact-skip-button", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_MIN_DURATION("sb-min-duration", FLOAT, 0F, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_VIDEO_LENGTH_WITHOUT_SEGMENTS("sb-length-without-segments", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_API_URL("sb-api-host-url", STRING, "https://sponsor.ajay.app", SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_TOAST_ON_SKIP("show-toast", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_AUTO_HIDE_SKIP_BUTTON("sb-auto-hide-skip-segment-button", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_TRACK_SKIP_COUNT("count-skips", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_ADJUST_NEW_SEGMENT_STEP("new-segment-step-accuracy", INTEGER, 150, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_LAST_VIP_CHECK("sb-last-vip-check", LONG, 0L, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_IS_VIP("sb-is-vip", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS("sb-skipped-segments", INTEGER, 0, SPONSOR_BLOCK),
|
||||||
|
@Deprecated
|
||||||
|
DEPRECATED_SB_LOCAL_TIME_SAVED_MILLISECONDS("sb-skipped-segments-time", LONG, 0L, SPONSOR_BLOCK);
|
||||||
|
//
|
||||||
|
// TODO END
|
||||||
|
//
|
||||||
|
|
||||||
|
private static SettingsEnum[] parents(SettingsEnum... parents) {
|
||||||
return parents;
|
return parents;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,26 +393,32 @@ public enum SettingsEnum {
|
|||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue) {
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, null, null);
|
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||||
boolean rebootApp) {
|
boolean rebootApp) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, null,null);
|
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||||
String userDialogMessage) {
|
String userDialogMessage) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, userDialogMessage, null);
|
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, userDialogMessage, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||||
SettingsEnum[] parents) {
|
SettingsEnum[] parents) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, null, parents);
|
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, null, parents);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||||
boolean rebootApp, String userDialogMessage) {
|
boolean rebootApp, String userDialogMessage) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, userDialogMessage, null);
|
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, userDialogMessage, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||||
boolean rebootApp, SettingsEnum[] parents) {
|
boolean rebootApp, SettingsEnum[] parents) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, null, parents);
|
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, null, parents);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||||
boolean rebootApp, String userDialogMessage, SettingsEnum[] parents) {
|
boolean rebootApp, String userDialogMessage, SettingsEnum[] parents) {
|
||||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, userDialogMessage, parents);
|
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, userDialogMessage, parents);
|
||||||
@ -234,20 +427,24 @@ public enum SettingsEnum {
|
|||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName) {
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName) {
|
||||||
this(path, returnType, defaultValue, prefName, false, null, null);
|
this(path, returnType, defaultValue, prefName, false, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
||||||
boolean rebootApp) {
|
boolean rebootApp) {
|
||||||
this(path, returnType, defaultValue, prefName, rebootApp, null, null);
|
this(path, returnType, defaultValue, prefName, rebootApp, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
||||||
String userDialogMessage) {
|
String userDialogMessage) {
|
||||||
this(path, returnType, defaultValue, prefName, false, userDialogMessage, null);
|
this(path, returnType, defaultValue, prefName, false, userDialogMessage, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
||||||
SettingsEnum[] parents) {
|
SettingsEnum[] parents) {
|
||||||
this(path, returnType, defaultValue, prefName, false, null, parents);
|
this(path, returnType, defaultValue, prefName, false, null, parents);
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
||||||
boolean rebootApp, @Nullable String userDialogMessage, @Nullable SettingsEnum[] parents) {
|
boolean rebootApp, @Nullable String userDialogMessage, @Nullable SettingsEnum[] parents) {
|
||||||
this.path = Objects.requireNonNull(path);
|
this.path = Objects.requireNonNull(path);
|
||||||
this.returnType = Objects.requireNonNull(returnType);
|
this.returnType = Objects.requireNonNull(returnType);
|
||||||
this.value = this.defaultValue = Objects.requireNonNull(defaultValue);
|
this.value = this.defaultValue = Objects.requireNonNull(defaultValue);
|
||||||
@ -273,22 +470,126 @@ public enum SettingsEnum {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final Map<String, SettingsEnum> pathToSetting = new HashMap<>(2* values().length);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
loadAllSettings();
|
loadAllSettings();
|
||||||
|
|
||||||
|
for (SettingsEnum setting : values()) {
|
||||||
|
pathToSetting.put(setting.path, setting);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public static SettingsEnum settingFromPath(@NonNull String str) {
|
public static SettingsEnum settingFromPath(@NonNull String str) {
|
||||||
for (SettingsEnum setting : values()) {
|
return pathToSetting.get(str);
|
||||||
if (setting.path.equals(str)) return setting;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void loadAllSettings() {
|
private static void loadAllSettings() {
|
||||||
for (SettingsEnum setting : values()) {
|
for (SettingsEnum setting : values()) {
|
||||||
setting.load();
|
setting.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// TODO: eventually delete this
|
||||||
|
// renamed settings with new path names, but otherwise the new and old settings are identical
|
||||||
|
//
|
||||||
|
SettingsEnum[][] renamedSettings = {
|
||||||
|
// TODO: do _not_ delete this SB private user id migration property until sometime in 2024.
|
||||||
|
// This is the only setting that cannot be reconfigured if lost,
|
||||||
|
// and more time should be given for users who rarely upgrade.
|
||||||
|
{DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID},
|
||||||
|
|
||||||
|
// TODO: delete the rest of these migration settings. When to delete? Anytime.
|
||||||
|
{DEPRECATED_ADREMOVER_BUTTONED_REMOVAL, HIDE_BUTTONED_ADS},
|
||||||
|
{DEPRECATED_ADREMOVER_GENERAL_ADS_REMOVAL, HIDE_GENERAL_ADS},
|
||||||
|
{DEPRECATED_ADREMOVER_HIDE_LATEST_POSTS, HIDE_HIDE_LATEST_POSTS},
|
||||||
|
{DEPRECATED_ADREMOVER_PAID_CONTENT, HIDE_PAID_CONTENT},
|
||||||
|
{DEPRECATED_ADREMOVER_SELF_SPONSOR, HIDE_SELF_SPONSOR},
|
||||||
|
{DEPRECATED_REMOVE_VIDEO_ADS, HIDE_VIDEO_ADS},
|
||||||
|
{DEPRECATED_ADREMOVER_CUSTOM_ENABLED, CUSTOM_FILTER},
|
||||||
|
{DEPRECATED_ADREMOVER_CUSTOM_REMOVAL, CUSTOM_FILTER_STRINGS},
|
||||||
|
|
||||||
|
{DEPRECATED_HIDE_CHANNEL_MEMBER_SHELF, HIDE_CHANNEL_MEMBER_SHELF},
|
||||||
|
{DEPRECATED_HIDE_CHAPTER_TEASER, HIDE_CHAPTER_TEASER},
|
||||||
|
{DEPRECATED_HIDE_COMMUNITY_GUIDELINES, HIDE_COMMUNITY_GUIDELINES},
|
||||||
|
{DEPRECATED_HIDE_COMMUNITY_POSTS, HIDE_COMMUNITY_POSTS},
|
||||||
|
{DEPRECATED_HIDE_COMPACT_BANNER, HIDE_COMPACT_BANNER},
|
||||||
|
{DEPRECATED_HIDE_EMERGENCY_BOX, HIDE_EMERGENCY_BOX},
|
||||||
|
{DEPRECATED_HIDE_FEED_SURVEY_REMOVAL, HIDE_FEED_SURVEY},
|
||||||
|
{DEPRECATED_HIDE_GRAY_SEPARATOR, HIDE_GRAY_SEPARATOR},
|
||||||
|
{DEPRECATED_HIDE_HIDE_CHANNEL_GUIDELINES, HIDE_HIDE_CHANNEL_GUIDELINES},
|
||||||
|
{DEPRECATED_HIDE_INFO_PANEL_REMOVAL, HIDE_HIDE_INFO_PANELS},
|
||||||
|
{DEPRECATED_HIDE_MEDICAL_PANEL_REMOVAL, HIDE_MEDICAL_PANELS},
|
||||||
|
{DEPRECATED_HIDE_MERCHANDISE_REMOVAL, HIDE_MERCHANDISE_BANNERS},
|
||||||
|
{DEPRECATED_HIDE_MOVIE_REMOVAL, HIDE_MOVIES_SECTION},
|
||||||
|
{DEPRECATED_HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES_REMOVAL, HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES},
|
||||||
|
{DEPRECATED_HIDE_VIEW_PRODUCTS, HIDE_PRODUCTS_BANNER},
|
||||||
|
{DEPRECATED_HIDE_WEB_SEARCH_RESULTS, HIDE_WEB_SEARCH_RESULTS},
|
||||||
|
{DEPRECATED_HIDE_SHORTS, HIDE_SHORTS},
|
||||||
|
{DEPRECATED_DISABLE_RESUMING_SHORTS_PLAYER, DISABLE_RESUMING_SHORTS_PLAYER},
|
||||||
|
{DEPRECATED_HIDE_INFO_CARDS, HIDE_INFO_CARDS},
|
||||||
|
|
||||||
|
{DEPRECATED_ETERNAL_DOWNLOADER, EXTERNAL_DOWNLOADER},
|
||||||
|
{DEPRECATED_EXTERNAL_DOWNLOADER_PACKAGE_NAME, EXTERNAL_DOWNLOADER_PACKAGE_NAME},
|
||||||
|
{DEPRECATED_COPY_VIDEO_URL, COPY_VIDEO_URL},
|
||||||
|
{DEPRECATED_COPY_VIDEO_URL_TIMESTAMP, COPY_VIDEO_URL_TIMESTAMP},
|
||||||
|
|
||||||
|
{DEPRECATED_SHOW_OLD_VIDEO_MENU, SHOW_OLD_VIDEO_MENU},
|
||||||
|
{DEPRECATED_VIDEO_QUALITY_DEFAULT_WIFI, VIDEO_QUALITY_DEFAULT_WIFI},
|
||||||
|
{DEPRECATED_VIDEO_QUALITY_DEFAULT_MOBILE, VIDEO_QUALITY_DEFAULT_MOBILE},
|
||||||
|
{DEPRECATED_PLAYBACK_SPEED_DEFAULT, PLAYBACK_SPEED_DEFAULT},
|
||||||
|
|
||||||
|
{DEPRECATED_AUTO_CAPTIONS, AUTO_CAPTIONS},
|
||||||
|
{DEPRECATED_PLAYER_POPUP_PANELS, PLAYER_POPUP_PANELS},
|
||||||
|
{DEPRECATED_SWIPE_BRIGHTNESS, SWIPE_BRIGHTNESS},
|
||||||
|
{DEPRECATED_SWIPE_VOLUME, SWIPE_VOLUME},
|
||||||
|
{DEPRECATED_PRESS_TO_SWIPE, SWIPE_PRESS_TO_ENGAGE},
|
||||||
|
{DEPRECATED_SWIPE_HAPTIC_FEEDBACK, SWIPE_HAPTIC_FEEDBACK},
|
||||||
|
|
||||||
|
{DEPRECATED_DEBUG, DEBUG},
|
||||||
|
{DEPRECATED_DEBUG_STACKTRACE, DEBUG_STACKTRACE},
|
||||||
|
{DEPRECATED_DEBUG_TOAST_ON_ERROR, DEBUG_TOAST_ON_ERROR},
|
||||||
|
|
||||||
|
{DEPRECATED_EXTERNAL_BROWSER, EXTERNAL_BROWSER},
|
||||||
|
{DEPRECATED_AUTO_REPEAT, AUTO_REPEAT},
|
||||||
|
{DEPRECATED_TAP_SEEKING, SEEKBAR_TAPPING},
|
||||||
|
{DEPRECATED_HDR_AUTO_BRIGHTNESS, HDR_AUTO_BRIGHTNESS},
|
||||||
|
|
||||||
|
{DEPRECATED_RYD_USER_ID, RYD_USER_ID},
|
||||||
|
{DEPRECATED_RYD_DISLIKE_PERCENTAGE, RYD_DISLIKE_PERCENTAGE},
|
||||||
|
{DEPRECATED_RYD_COMPACT_LAYOUT, RYD_COMPACT_LAYOUT},
|
||||||
|
|
||||||
|
{DEPRECATED_SB_ENABLED, SB_ENABLED},
|
||||||
|
{DEPRECATED_SB_VOTING_BUTTON, SB_VOTING_BUTTON},
|
||||||
|
{DEPRECATED_SB_CREATE_NEW_SEGMENT, SB_CREATE_NEW_SEGMENT},
|
||||||
|
{DEPRECATED_SB_COMPACT_SKIP_BUTTON, SB_COMPACT_SKIP_BUTTON},
|
||||||
|
{DEPRECATED_SB_MIN_DURATION, SB_SEGMENT_MIN_DURATION},
|
||||||
|
{DEPRECATED_SB_VIDEO_LENGTH_WITHOUT_SEGMENTS, SB_VIDEO_LENGTH_WITHOUT_SEGMENTS},
|
||||||
|
{DEPRECATED_SB_API_URL, SB_API_URL},
|
||||||
|
{DEPRECATED_SB_TOAST_ON_SKIP, SB_TOAST_ON_SKIP},
|
||||||
|
{DEPRECATED_SB_AUTO_HIDE_SKIP_BUTTON, SB_AUTO_HIDE_SKIP_BUTTON},
|
||||||
|
{DEPRECATED_SB_TRACK_SKIP_COUNT, SB_TRACK_SKIP_COUNT},
|
||||||
|
{DEPRECATED_SB_ADJUST_NEW_SEGMENT_STEP, SB_CREATE_NEW_SEGMENT_STEP},
|
||||||
|
{DEPRECATED_SB_LAST_VIP_CHECK, SB_LAST_VIP_CHECK},
|
||||||
|
{DEPRECATED_SB_IS_VIP, SB_USER_IS_VIP},
|
||||||
|
{DEPRECATED_SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS, SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS},
|
||||||
|
{DEPRECATED_SB_LOCAL_TIME_SAVED_MILLISECONDS, SB_LOCAL_TIME_SAVED_MILLISECONDS},
|
||||||
|
};
|
||||||
|
for (SettingsEnum[] oldNewSetting : renamedSettings) {
|
||||||
|
SettingsEnum oldSetting = oldNewSetting[0];
|
||||||
|
SettingsEnum newSetting = oldNewSetting[1];
|
||||||
|
|
||||||
|
if (!oldSetting.isSetToDefault()) {
|
||||||
|
LogHelper.printInfo(() -> "Migrating old setting of '" + oldSetting.value
|
||||||
|
+ "' from: " + oldSetting + " into replacement setting: " + newSetting);
|
||||||
|
newSetting.saveValue(oldSetting.value);
|
||||||
|
oldSetting.saveValue(oldSetting.defaultValue); // reset old value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// TODO end
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
private void load() {
|
private void load() {
|
||||||
@ -315,10 +616,10 @@ public enum SettingsEnum {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets, but does _not_ persistently save the value.
|
* Sets, but does _not_ persistently save the value.
|
||||||
*
|
* <p>
|
||||||
* This intentionally is a static method, to deter accidental usage
|
* This intentionally is a static method, to deter accidental usage
|
||||||
* when {@link #saveValue(Object)} was intended.
|
* when {@link #saveValue(Object)} was intended.
|
||||||
*
|
* <p>
|
||||||
* This method is only to be used by the Settings preference code.
|
* This method is only to be used by the Settings preference code.
|
||||||
*/
|
*/
|
||||||
public static void setValue(@NonNull SettingsEnum setting, @NonNull String newValue) {
|
public static void setValue(@NonNull SettingsEnum setting, @NonNull String newValue) {
|
||||||
@ -343,11 +644,12 @@ public enum SettingsEnum {
|
|||||||
throw new IllegalStateException(setting.name());
|
throw new IllegalStateException(setting.name());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is only to be used by the Settings preference code.
|
* This method is only to be used by the Settings preference code.
|
||||||
*/
|
*/
|
||||||
public static void setValue(@NonNull SettingsEnum setting, @NonNull Boolean newValue) {
|
public static void setValue(@NonNull SettingsEnum setting, @NonNull Boolean newValue) {
|
||||||
Objects.requireNonNull(newValue);
|
setting.returnType.validate(newValue);
|
||||||
setting.value = newValue;
|
setting.value = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,7 +657,8 @@ public enum SettingsEnum {
|
|||||||
* Sets the value, and persistently saves it.
|
* Sets the value, and persistently saves it.
|
||||||
*/
|
*/
|
||||||
public void saveValue(@NonNull Object newValue) {
|
public void saveValue(@NonNull Object newValue) {
|
||||||
Objects.requireNonNull(newValue);
|
returnType.validate(newValue);
|
||||||
|
value = newValue; // Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
||||||
switch (returnType) {
|
switch (returnType) {
|
||||||
case BOOLEAN:
|
case BOOLEAN:
|
||||||
sharedPref.saveBoolean(path, (boolean) newValue);
|
sharedPref.saveBoolean(path, (boolean) newValue);
|
||||||
@ -375,12 +678,11 @@ public enum SettingsEnum {
|
|||||||
default:
|
default:
|
||||||
throw new IllegalStateException(name());
|
throw new IllegalStateException(name());
|
||||||
}
|
}
|
||||||
value = newValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return if this setting can be configured and used.
|
* @return if this setting can be configured and used.
|
||||||
*
|
* <p>
|
||||||
* Not to be confused with {@link #getBoolean()}
|
* Not to be confused with {@link #getBoolean()}
|
||||||
*/
|
*/
|
||||||
public boolean isAvailable() {
|
public boolean isAvailable() {
|
||||||
@ -393,6 +695,13 @@ public enum SettingsEnum {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if the currently set value is the same as {@link #defaultValue}
|
||||||
|
*/
|
||||||
|
public boolean isSetToDefault() {
|
||||||
|
return value.equals(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getBoolean() {
|
public boolean getBoolean() {
|
||||||
return (Boolean) value;
|
return (Boolean) value;
|
||||||
}
|
}
|
||||||
@ -422,11 +731,174 @@ public enum SettingsEnum {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This could be yet another field,
|
||||||
|
* for now use a simple switch statement since this method is not used outside this class.
|
||||||
|
*/
|
||||||
|
private boolean includeWithImportExport() {
|
||||||
|
switch (this) {
|
||||||
|
case RYD_USER_ID: // Not useful to export, no reason to include it.
|
||||||
|
case SB_LAST_VIP_CHECK:
|
||||||
|
case SB_HIDE_EXPORT_WARNING:
|
||||||
|
case SB_SEEN_GUIDELINES:
|
||||||
|
case SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS:
|
||||||
|
case SB_LOCAL_TIME_SAVED_MILLISECONDS:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin import / export
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a setting path has this prefix, then remove it before importing/exporting.
|
||||||
|
*/
|
||||||
|
private static final String OPTIONAL_REVANCED_SETTINGS_PREFIX = "revanced_";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The path, minus any 'revanced' prefix to keep json concise.
|
||||||
|
*/
|
||||||
|
private String getImportExportKey() {
|
||||||
|
if (path.startsWith(OPTIONAL_REVANCED_SETTINGS_PREFIX)) {
|
||||||
|
return path.substring(OPTIONAL_REVANCED_SETTINGS_PREFIX.length());
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SettingsEnum[] valuesSortedForExport() {
|
||||||
|
SettingsEnum[] sorted = values();
|
||||||
|
Arrays.sort(sorted, (SettingsEnum o1, SettingsEnum o2) -> {
|
||||||
|
// Organize SponsorBlock settings last.
|
||||||
|
final boolean o1IsSb = o1.sharedPref == SPONSOR_BLOCK;
|
||||||
|
final boolean o2IsSb = o2.sharedPref == SPONSOR_BLOCK;
|
||||||
|
if (o1IsSb != o2IsSb) {
|
||||||
|
return o1IsSb ? 1 : -1;
|
||||||
|
}
|
||||||
|
return o1.path.compareTo(o2.path);
|
||||||
|
});
|
||||||
|
return sorted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String exportJSON(@Nullable Context alertDialogContext) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
for (SettingsEnum setting : valuesSortedForExport()) {
|
||||||
|
String importExportKey = setting.getImportExportKey();
|
||||||
|
if (json.has(importExportKey)) {
|
||||||
|
throw new IllegalArgumentException("duplicate key found: " + importExportKey);
|
||||||
|
}
|
||||||
|
final boolean exportDefaultValues = false; // Enable to see what all settings looks like in the UI.
|
||||||
|
if (setting.includeWithImportExport() && (!setting.isSetToDefault() | exportDefaultValues)) {
|
||||||
|
json.put(importExportKey, setting.getObjectValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SponsorBlockSettings.exportCategoriesToFlatJson(alertDialogContext, json);
|
||||||
|
|
||||||
|
if (json.length() == 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String export = json.toString(0);
|
||||||
|
// Remove the outer JSON braces to make the output more compact,
|
||||||
|
// and leave less chance of the user forgetting to copy it
|
||||||
|
return export.substring(2, export.length() - 2);
|
||||||
|
} catch (JSONException e) {
|
||||||
|
LogHelper.printException(() -> "Export failure", e); // should never happen
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if any settings that require a reboot were changed.
|
||||||
|
*/
|
||||||
|
public static boolean importJSON(@NonNull String settingsJsonString) {
|
||||||
|
try {
|
||||||
|
if (!settingsJsonString.matches("[\\s\\S]*\\{")) {
|
||||||
|
settingsJsonString = '{' + settingsJsonString + '}'; // Restore outer JSON braces
|
||||||
|
}
|
||||||
|
JSONObject json = new JSONObject(settingsJsonString);
|
||||||
|
|
||||||
|
boolean rebootSettingChanged = false;
|
||||||
|
int numberOfSettingsImported = 0;
|
||||||
|
for (SettingsEnum setting : values()) {
|
||||||
|
String key = setting.getImportExportKey();
|
||||||
|
if (json.has(key)) {
|
||||||
|
Object value;
|
||||||
|
switch (setting.returnType) {
|
||||||
|
case BOOLEAN:
|
||||||
|
value = json.getBoolean(key);
|
||||||
|
break;
|
||||||
|
case INTEGER:
|
||||||
|
value = json.getInt(key);
|
||||||
|
break;
|
||||||
|
case LONG:
|
||||||
|
value = json.getLong(key);
|
||||||
|
break;
|
||||||
|
case FLOAT:
|
||||||
|
value = (float) json.getDouble(key);
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
value = json.getString(key);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
if (!setting.getObjectValue().equals(value)) {
|
||||||
|
rebootSettingChanged |= setting.rebootApp;
|
||||||
|
setting.saveValue(value);
|
||||||
|
}
|
||||||
|
numberOfSettingsImported++;
|
||||||
|
} else if (setting.includeWithImportExport() && !setting.isSetToDefault()) {
|
||||||
|
LogHelper.printDebug(() -> "Resetting to default: " + setting);
|
||||||
|
rebootSettingChanged |= setting.rebootApp;
|
||||||
|
setting.saveValue(setting.defaultValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
numberOfSettingsImported += SponsorBlockSettings.importCategoriesFromFlatJson(json);
|
||||||
|
|
||||||
|
ReVancedUtils.showToastLong(numberOfSettingsImported == 0
|
||||||
|
? str("revanced_settings_import_reset")
|
||||||
|
: str("revanced_settings_import_success", numberOfSettingsImported));
|
||||||
|
|
||||||
|
return rebootSettingChanged;
|
||||||
|
} catch (JSONException | IllegalArgumentException ex) {
|
||||||
|
ReVancedUtils.showToastLong(str("revanced_settings_import_failure_parse", ex.getMessage()));
|
||||||
|
LogHelper.printInfo(() -> "", ex);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "Import failure: " + ex.getMessage(), ex); // should never happen
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End import / export
|
||||||
|
|
||||||
public enum ReturnType {
|
public enum ReturnType {
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
INTEGER,
|
INTEGER,
|
||||||
STRING,
|
|
||||||
LONG,
|
LONG,
|
||||||
FLOAT,
|
FLOAT,
|
||||||
|
STRING;
|
||||||
|
|
||||||
|
public void validate(@Nullable Object obj) throws IllegalArgumentException {
|
||||||
|
if (!matches(obj)) {
|
||||||
|
throw new IllegalArgumentException("'" + obj + "' does not match:" + this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean matches(@Nullable Object obj) {
|
||||||
|
switch (this) {
|
||||||
|
case BOOLEAN:
|
||||||
|
return obj instanceof Boolean;
|
||||||
|
case INTEGER:
|
||||||
|
return obj instanceof Integer;
|
||||||
|
case LONG:
|
||||||
|
return obj instanceof Long;
|
||||||
|
case FLOAT:
|
||||||
|
return obj instanceof Float;
|
||||||
|
case STRING:
|
||||||
|
return obj instanceof String;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,6 +36,11 @@ public enum SharedPrefCategory {
|
|||||||
preferences = Objects.requireNonNull(ReVancedUtils.getContext()).getSharedPreferences(prefName, Context.MODE_PRIVATE);
|
preferences = Objects.requireNonNull(ReVancedUtils.getContext()).getSharedPreferences(prefName, Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void removeConflictingPreferenceKeyValue(@NonNull String key) {
|
||||||
|
LogHelper.printException(() -> "Found conflicting preference: " + key);
|
||||||
|
preferences.edit().remove(key).apply();
|
||||||
|
}
|
||||||
|
|
||||||
private void saveObjectAsString(@NonNull String key, @Nullable Object value) {
|
private void saveObjectAsString(@NonNull String key, @Nullable Object value) {
|
||||||
preferences.edit().putString(key, (value == null ? null : value.toString())).apply();
|
preferences.edit().putString(key, (value == null ? null : value.toString())).apply();
|
||||||
}
|
}
|
||||||
@ -91,7 +97,14 @@ public enum SharedPrefCategory {
|
|||||||
}
|
}
|
||||||
return _default;
|
return _default;
|
||||||
} catch (ClassCastException ex) {
|
} catch (ClassCastException ex) {
|
||||||
return preferences.getInt(key, _default); // old data, previously stored as primitive
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +117,12 @@ public enum SharedPrefCategory {
|
|||||||
}
|
}
|
||||||
return _default;
|
return _default;
|
||||||
} catch (ClassCastException ex) {
|
} catch (ClassCastException ex) {
|
||||||
return preferences.getLong(key, _default);
|
try {
|
||||||
|
return preferences.getLong(key, _default);
|
||||||
|
} catch (ClassCastException ex2) {
|
||||||
|
removeConflictingPreferenceKeyValue(key);
|
||||||
|
return _default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +135,12 @@ public enum SharedPrefCategory {
|
|||||||
}
|
}
|
||||||
return _default;
|
return _default;
|
||||||
} catch (ClassCastException ex) {
|
} catch (ClassCastException ex) {
|
||||||
return preferences.getFloat(key, _default);
|
try {
|
||||||
|
return preferences.getFloat(key, _default);
|
||||||
|
} catch (ClassCastException ex2) {
|
||||||
|
removeConflictingPreferenceKeyValue(key);
|
||||||
|
return _default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
package app.revanced.integrations.settingsmenu;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.utils.StringRef.str;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
|
import android.preference.Preference;
|
||||||
|
import android.text.InputType;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.util.TypedValue;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
||||||
|
|
||||||
|
private String existingSettings;
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
setSelectable(true);
|
||||||
|
|
||||||
|
EditText editText = getEditText();
|
||||||
|
editText.setTextIsSelectable(true);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
editText.setAutofillHints((String) null);
|
||||||
|
}
|
||||||
|
editText.setInputType(editText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
|
editText.setTextSize(TypedValue.COMPLEX_UNIT_PT, 7); // Use a smaller font to reduce text wrap.
|
||||||
|
|
||||||
|
setOnPreferenceClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImportExportPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
public ImportExportPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
public ImportExportPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
public ImportExportPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
|
try {
|
||||||
|
// Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened.
|
||||||
|
existingSettings = SettingsEnum.exportJSON(getContext());
|
||||||
|
getEditText().setText(existingSettings);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "showDialog failure", ex);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
|
try {
|
||||||
|
// Show the user the settings in JSON format.
|
||||||
|
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> {
|
||||||
|
ReVancedUtils.setClipboard(getEditText().getText().toString());
|
||||||
|
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> {
|
||||||
|
importSettings(getEditText().getText().toString());
|
||||||
|
});
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "onPrepareDialogBuilder failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importSettings(String replacementSettings) {
|
||||||
|
try {
|
||||||
|
if (replacementSettings.equals(existingSettings)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ReVancedSettingsFragment.settingImportInProgress = true;
|
||||||
|
final boolean rebootNeeded = SettingsEnum.importJSON(replacementSettings);
|
||||||
|
if (rebootNeeded) {
|
||||||
|
ReVancedSettingsFragment.showRebootDialog(getContext());
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "importSettings failure", ex);
|
||||||
|
} finally {
|
||||||
|
ReVancedSettingsFragment.settingImportInProgress = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,17 +1,18 @@
|
|||||||
package app.revanced.integrations.settingsmenu;
|
package app.revanced.integrations.settingsmenu;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.utils.ReVancedUtils.getChildView;
|
||||||
|
import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.Activity;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import java.util.Objects;
|
||||||
|
|
||||||
import com.google.android.libraries.social.licenses.LicenseActivity;
|
|
||||||
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
import app.revanced.integrations.utils.ThemeHelper;
|
import app.revanced.integrations.utils.ThemeHelper;
|
||||||
|
|
||||||
public class ReVancedSettingActivity {
|
public class ReVancedSettingActivity {
|
||||||
@ -19,76 +20,68 @@ public class ReVancedSettingActivity {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void setTheme(LicenseActivity base) {
|
public static void initializeSettings(Activity licenseActivity) {
|
||||||
final var whiteTheme = "Theme.YouTube.Settings";
|
|
||||||
final var darkTheme = "Theme.YouTube.Settings.Dark";
|
|
||||||
|
|
||||||
final var theme = ThemeHelper.isDarkTheme() ? darkTheme : whiteTheme;
|
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Using theme: " + theme);
|
|
||||||
base.setTheme(ReVancedUtils.getResourceIdentifier(theme, "style"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void initializeSettings(LicenseActivity base) {
|
|
||||||
base.setContentView(ReVancedUtils.getResourceIdentifier("revanced_settings_with_toolbar", "layout"));
|
|
||||||
|
|
||||||
PreferenceFragment preferenceFragment;
|
|
||||||
String preferenceIdentifier;
|
|
||||||
|
|
||||||
String dataString = base.getIntent().getDataString();
|
|
||||||
if (dataString.equalsIgnoreCase("sponsorblock_settings")) {
|
|
||||||
preferenceIdentifier = "sb_settings";
|
|
||||||
preferenceFragment = new SponsorBlockSettingsFragment();
|
|
||||||
} else if (dataString.equalsIgnoreCase("ryd_settings")) {
|
|
||||||
preferenceIdentifier = "revanced_ryd_settings_title";
|
|
||||||
preferenceFragment = new ReturnYouTubeDislikeSettingsFragment();
|
|
||||||
} else {
|
|
||||||
preferenceIdentifier = "revanced_settings";
|
|
||||||
preferenceFragment = new ReVancedSettingsFragment();
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TextView toolbar = getTextView((ViewGroup) base.findViewById(ReVancedUtils.getResourceIdentifier("toolbar", "id")));
|
ThemeHelper.setActivityTheme(licenseActivity);
|
||||||
if (toolbar == null) {
|
licenseActivity.setContentView(
|
||||||
// FIXME
|
getResourceIdentifier("revanced_settings_with_toolbar", "layout"));
|
||||||
// https://github.com/revanced/revanced-patches/issues/1384
|
setBackButton(licenseActivity);
|
||||||
LogHelper.printDebug(() -> "Could not find toolbar");
|
|
||||||
} else {
|
PreferenceFragment fragment;
|
||||||
toolbar.setText(preferenceIdentifier);
|
String toolbarTitleResourceName;
|
||||||
|
String dataString = licenseActivity.getIntent().getDataString();
|
||||||
|
switch (dataString) {
|
||||||
|
case "sponsorblock_settings":
|
||||||
|
toolbarTitleResourceName = "revanced_sponsorblock_settings_title";
|
||||||
|
fragment = new SponsorBlockSettingsFragment();
|
||||||
|
break;
|
||||||
|
case "ryd_settings":
|
||||||
|
toolbarTitleResourceName = "revanced_ryd_settings_title";
|
||||||
|
fragment = new ReturnYouTubeDislikeSettingsFragment();
|
||||||
|
break;
|
||||||
|
case "revanced_settings":
|
||||||
|
toolbarTitleResourceName = "revanced_settings_title";
|
||||||
|
fragment = new ReVancedSettingsFragment();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogHelper.printException(() -> "Unknown setting: " + dataString);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
|
||||||
LogHelper.printException(() -> "Could not set Toolbar title", e);
|
setToolbarTitle(licenseActivity, toolbarTitleResourceName);
|
||||||
|
licenseActivity.getFragmentManager()
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(getResourceIdentifier("revanced_settings_fragments", "id"), fragment)
|
||||||
|
.commit();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "onCreate failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
base.getFragmentManager().beginTransaction().replace(ReVancedUtils.getResourceIdentifier("revanced_settings_fragments", "id"), preferenceFragment).commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setToolbarTitle(Activity activity, String toolbarTitleResourceName) {
|
||||||
|
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
||||||
|
TextView toolbarTextView = Objects.requireNonNull(getChildView(toolbar, view -> view instanceof TextView));
|
||||||
|
toolbarTextView.setText(getResourceIdentifier(toolbarTitleResourceName, "string"));
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@SuppressLint("UseCompatLoadingForDrawables")
|
||||||
public static <T extends View> T getView(Class<T> typeClass, ViewGroup viewGroup) {
|
private static void setBackButton(Activity activity) {
|
||||||
if (viewGroup == null) {
|
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
||||||
return null;
|
ImageButton imageButton = Objects.requireNonNull(getChildView(toolbar, view -> view instanceof ImageButton));
|
||||||
|
final int backButtonResource = getResourceIdentifier(ThemeHelper.isDarkTheme()
|
||||||
|
? "yt_outline_arrow_left_white_24"
|
||||||
|
: "yt_outline_arrow_left_black_24",
|
||||||
|
"drawable");
|
||||||
|
imageButton.setImageDrawable(activity.getResources().getDrawable(backButtonResource));
|
||||||
|
imageButton.setOnClickListener(view -> activity.onBackPressed());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getToolbarResourceId() {
|
||||||
|
final int toolbarResourceId = getResourceIdentifier("revanced_toolbar", "id");
|
||||||
|
if (toolbarResourceId == 0) {
|
||||||
|
throw new IllegalStateException("Could not find back button resource");
|
||||||
}
|
}
|
||||||
int childCount = viewGroup.getChildCount();
|
return toolbarResourceId;
|
||||||
for (int i = 0; i < childCount; i++) {
|
|
||||||
View childAt = viewGroup.getChildAt(i);
|
|
||||||
if (childAt.getClass() == typeClass) {
|
|
||||||
return (T) childAt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static ImageButton getImageButton(ViewGroup viewGroup) {
|
|
||||||
return getView(ImageButton.class, viewGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static TextView getTextView(ViewGroup viewGroup) {
|
|
||||||
return getView(TextView.class, viewGroup);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -24,13 +24,40 @@ import androidx.annotation.Nullable;
|
|||||||
|
|
||||||
import com.google.android.apps.youtube.app.application.Shell_HomeActivity;
|
import com.google.android.apps.youtube.app.application.Shell_HomeActivity;
|
||||||
|
|
||||||
import app.revanced.integrations.patches.playback.speed.RememberPlaybackSpeedPatch;
|
import app.revanced.integrations.patches.playback.speed.CustomVideoSpeedPatch;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.settings.SharedPrefCategory;
|
import app.revanced.integrations.settings.SharedPrefCategory;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public class ReVancedSettingsFragment extends PreferenceFragment {
|
public class ReVancedSettingsFragment extends PreferenceFragment {
|
||||||
|
/**
|
||||||
|
* Indicates that if a preference changes,
|
||||||
|
* to apply the change from the Setting to the UI component.
|
||||||
|
*/
|
||||||
|
static boolean settingImportInProgress;
|
||||||
|
|
||||||
|
private static void reboot(@NonNull Context activity) {
|
||||||
|
final int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
|
||||||
|
PendingIntent intent = PendingIntent.getActivity(activity, 0,
|
||||||
|
new Intent(activity, Shell_HomeActivity.class), intentFlags);
|
||||||
|
AlarmManager systemService = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
|
||||||
|
systemService.setExact(AlarmManager.ELAPSED_REALTIME, 1500L, intent);
|
||||||
|
Process.killProcess(Process.myPid());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void showRebootDialog(@NonNull Context activity) {
|
||||||
|
String positiveButton = str("in_app_update_restart_button");
|
||||||
|
String negativeButton = str("sign_in_cancel");
|
||||||
|
new AlertDialog.Builder(activity).setMessage(str("pref_refresh_config"))
|
||||||
|
.setPositiveButton(positiveButton, (dialog, id) -> {
|
||||||
|
reboot(activity);
|
||||||
|
})
|
||||||
|
.setNegativeButton(negativeButton, null)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
|
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
|
||||||
*/
|
*/
|
||||||
@ -42,33 +69,53 @@ public class ReVancedSettingsFragment extends PreferenceFragment {
|
|||||||
if (setting == null) {
|
if (setting == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Preference pref = this.findPreference(str);
|
Preference pref = findPreference(str);
|
||||||
LogHelper.printDebug(() -> "Setting " + setting.name() + " was changed. Preference " + str + ": " + pref);
|
LogHelper.printDebug(() -> setting.name() + ": " + " setting value:" + setting.getObjectValue() + " pref:" + pref);
|
||||||
|
if (pref == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (pref instanceof SwitchPreference) {
|
if (pref instanceof SwitchPreference) {
|
||||||
SwitchPreference switchPref = (SwitchPreference) pref;
|
SwitchPreference switchPref = (SwitchPreference) pref;
|
||||||
SettingsEnum.setValue(setting, switchPref.isChecked());
|
if (settingImportInProgress) {
|
||||||
|
switchPref.setChecked(setting.getBoolean());
|
||||||
|
} else {
|
||||||
|
SettingsEnum.setValue(setting, switchPref.isChecked());
|
||||||
|
}
|
||||||
} else if (pref instanceof EditTextPreference) {
|
} else if (pref instanceof EditTextPreference) {
|
||||||
String editText = ((EditTextPreference) pref).getText();
|
EditTextPreference editPreference = (EditTextPreference) pref;
|
||||||
SettingsEnum.setValue(setting, editText);
|
if (settingImportInProgress) {
|
||||||
|
editPreference.getEditText().setText(setting.getObjectValue().toString());
|
||||||
|
} else {
|
||||||
|
SettingsEnum.setValue(setting, editPreference.getText());
|
||||||
|
}
|
||||||
} else if (pref instanceof ListPreference) {
|
} else if (pref instanceof ListPreference) {
|
||||||
ListPreference listPref = (ListPreference) pref;
|
ListPreference listPref = (ListPreference) pref;
|
||||||
SettingsEnum.setValue(setting, listPref.getValue());
|
if (settingImportInProgress) {
|
||||||
|
listPref.setValue(setting.getObjectValue().toString());
|
||||||
|
} else {
|
||||||
|
SettingsEnum.setValue(setting, listPref.getValue());
|
||||||
|
}
|
||||||
updateListPreferenceSummary((ListPreference) pref, setting);
|
updateListPreferenceSummary((ListPreference) pref, setting);
|
||||||
} else {
|
} else {
|
||||||
LogHelper.printException(() -> "Setting cannot be handled: " + pref.getClass() + " " + pref);
|
LogHelper.printException(() -> "Setting cannot be handled: " + pref.getClass() + " " + pref);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enableDisablePreferences();
|
||||||
|
|
||||||
|
if (settingImportInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!showingUserDialogMessage) {
|
if (!showingUserDialogMessage) {
|
||||||
if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) {
|
if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) {
|
||||||
showSettingUserDialogConfirmation(getActivity(), (SwitchPreference) pref, setting);
|
showSettingUserDialogConfirmation(getActivity(), (SwitchPreference) pref, setting);
|
||||||
} else if (setting.rebootApp) {
|
} else if (setting.rebootApp) {
|
||||||
rebootDialog(getActivity());
|
showRebootDialog(getActivity());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enableDisablePreferences();
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
LogHelper.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||||
}
|
}
|
||||||
@ -88,20 +135,24 @@ public class ReVancedSettingsFragment extends PreferenceFragment {
|
|||||||
// if the preference was included, then initialize it based on the available playback speed
|
// if the preference was included, then initialize it based on the available playback speed
|
||||||
Preference defaultSpeedPreference = findPreference(SettingsEnum.PLAYBACK_SPEED_DEFAULT.path);
|
Preference defaultSpeedPreference = findPreference(SettingsEnum.PLAYBACK_SPEED_DEFAULT.path);
|
||||||
if (defaultSpeedPreference instanceof ListPreference) {
|
if (defaultSpeedPreference instanceof ListPreference) {
|
||||||
RememberPlaybackSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference);
|
CustomVideoSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference);
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the summary text for any ListPreferences
|
// Set current value from SettingsEnum
|
||||||
for (SettingsEnum setting : SettingsEnum.values()) {
|
for (SettingsEnum setting : SettingsEnum.values()) {
|
||||||
Preference preference = findPreference(setting.path);
|
Preference preference = findPreference(setting.path);
|
||||||
if (preference instanceof ListPreference) {
|
if (preference instanceof SwitchPreference) {
|
||||||
|
((SwitchPreference) preference).setChecked(setting.getBoolean());
|
||||||
|
} else if (preference instanceof EditTextPreference) {
|
||||||
|
((EditTextPreference) preference).setText(setting.getObjectValue().toString());
|
||||||
|
} else if (preference instanceof ListPreference) {
|
||||||
updateListPreferenceSummary((ListPreference) preference, setting);
|
updateListPreferenceSummary((ListPreference) preference, setting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
|
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "onActivityCreated() error", ex);
|
LogHelper.printException(() -> "onActivityCreated() failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,34 +171,18 @@ public class ReVancedSettingsFragment extends PreferenceFragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets summary text to the currently selected list option.
|
||||||
|
*/
|
||||||
private void updateListPreferenceSummary(ListPreference listPreference, SettingsEnum setting) {
|
private void updateListPreferenceSummary(ListPreference listPreference, SettingsEnum setting) {
|
||||||
final int entryIndex = listPreference.findIndexOfValue(setting.getObjectValue().toString());
|
String objectStringValue = setting.getObjectValue().toString();
|
||||||
|
final int entryIndex = listPreference.findIndexOfValue(objectStringValue);
|
||||||
if (entryIndex >= 0) {
|
if (entryIndex >= 0) {
|
||||||
listPreference.setSummary(listPreference.getEntries()[entryIndex]);
|
listPreference.setSummary(listPreference.getEntries()[entryIndex]);
|
||||||
|
listPreference.setValue(objectStringValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reboot(@NonNull Activity activity) {
|
|
||||||
final int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE;
|
|
||||||
PendingIntent intent = PendingIntent.getActivity(activity, 0,
|
|
||||||
new Intent(activity, Shell_HomeActivity.class), intentFlags);
|
|
||||||
AlarmManager systemService = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
|
|
||||||
systemService.setExact(AlarmManager.ELAPSED_REALTIME, 1500L, intent);
|
|
||||||
Process.killProcess(Process.myPid());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void rebootDialog(@NonNull Activity activity) {
|
|
||||||
String positiveButton = str("in_app_update_restart_button");
|
|
||||||
String negativeButton = str("sign_in_cancel");
|
|
||||||
new AlertDialog.Builder(activity).setMessage(str("pref_refresh_config"))
|
|
||||||
.setPositiveButton(positiveButton, (dialog, id) -> {
|
|
||||||
reboot(activity);
|
|
||||||
})
|
|
||||||
.setNegativeButton(negativeButton, null)
|
|
||||||
.setCancelable(false)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showSettingUserDialogConfirmation(@NonNull Activity activity, SwitchPreference switchPref, SettingsEnum setting) {
|
private void showSettingUserDialogConfirmation(@NonNull Activity activity, SwitchPreference switchPref, SettingsEnum setting) {
|
||||||
showingUserDialogMessage = true;
|
showingUserDialogMessage = true;
|
||||||
new AlertDialog.Builder(activity)
|
new AlertDialog.Builder(activity)
|
||||||
@ -155,7 +190,7 @@ public class ReVancedSettingsFragment extends PreferenceFragment {
|
|||||||
.setMessage(setting.userDialogMessage.toString())
|
.setMessage(setting.userDialogMessage.toString())
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
||||||
if (setting.rebootApp) {
|
if (setting.rebootApp) {
|
||||||
rebootDialog(activity);
|
showRebootDialog(activity);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package app.revanced.integrations.settingsmenu;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.utils.StringRef.str;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.EditTextPreference;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
|
||||||
|
public class ResettableEditTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
|
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
|
super(context, attrs, defStyleAttr, defStyleRes);
|
||||||
|
}
|
||||||
|
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
}
|
||||||
|
public ResettableEditTextPreference(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
}
|
||||||
|
public ResettableEditTextPreference(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
|
super.onPrepareDialogBuilder(builder);
|
||||||
|
SettingsEnum setting = SettingsEnum.settingFromPath(getKey());
|
||||||
|
if (setting != null) {
|
||||||
|
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void showDialog(Bundle state) {
|
||||||
|
super.showDialog(state);
|
||||||
|
|
||||||
|
// Override the button click listener to prevent dismissing the dialog.
|
||||||
|
Button button = ((AlertDialog) getDialog()).getButton(AlertDialog.BUTTON_NEUTRAL);
|
||||||
|
if (button == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
button.setOnClickListener(v -> {
|
||||||
|
try {
|
||||||
|
SettingsEnum setting = Objects.requireNonNull(SettingsEnum.settingFromPath(getKey()));
|
||||||
|
String defaultStringValue = setting.defaultValue.toString();
|
||||||
|
EditText editText = getEditText();
|
||||||
|
editText.setText(defaultStringValue);
|
||||||
|
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogHelper.printException(() -> "reset failure", ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,11 @@ import app.revanced.integrations.settings.SharedPrefCategory;
|
|||||||
|
|
||||||
public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If dislikes are shown on Shorts.
|
||||||
|
*/
|
||||||
|
private SwitchPreference shortsPreference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If dislikes are shown as percentage.
|
* If dislikes are shown as percentage.
|
||||||
*/
|
*/
|
||||||
@ -29,9 +34,16 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
|||||||
*/
|
*/
|
||||||
private SwitchPreference compactLayoutPreference;
|
private SwitchPreference compactLayoutPreference;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If segmented like/dislike button uses smaller compact layout.
|
||||||
|
*/
|
||||||
|
private SwitchPreference toastOnRYDNotAvailable;
|
||||||
|
|
||||||
private void updateUIState() {
|
private void updateUIState() {
|
||||||
percentagePreference.setEnabled(SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.isAvailable());
|
shortsPreference.setEnabled(SettingsEnum.RYD_SHORTS.isAvailable());
|
||||||
compactLayoutPreference.setEnabled(SettingsEnum.RYD_USE_COMPACT_LAYOUT.isAvailable());
|
percentagePreference.setEnabled(SettingsEnum.RYD_DISLIKE_PERCENTAGE.isAvailable());
|
||||||
|
compactLayoutPreference.setEnabled(SettingsEnum.RYD_COMPACT_LAYOUT.isAvailable());
|
||||||
|
toastOnRYDNotAvailable.setEnabled(SettingsEnum.RYD_TOAST_ON_CONNECTION_ERROR.isAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -58,13 +70,25 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
|||||||
});
|
});
|
||||||
preferenceScreen.addPreference(enabledPreference);
|
preferenceScreen.addPreference(enabledPreference);
|
||||||
|
|
||||||
|
shortsPreference = new SwitchPreference(context);
|
||||||
|
shortsPreference.setChecked(SettingsEnum.RYD_SHORTS.getBoolean());
|
||||||
|
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
|
||||||
|
shortsPreference.setSummaryOn(str("revanced_ryd_shorts_summary_on"));
|
||||||
|
shortsPreference.setSummaryOff(str("revanced_ryd_shorts_summary_off"));
|
||||||
|
shortsPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||||
|
SettingsEnum.RYD_SHORTS.saveValue(newValue);
|
||||||
|
updateUIState();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
preferenceScreen.addPreference(shortsPreference);
|
||||||
|
|
||||||
percentagePreference = new SwitchPreference(context);
|
percentagePreference = new SwitchPreference(context);
|
||||||
percentagePreference.setChecked(SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean());
|
percentagePreference.setChecked(SettingsEnum.RYD_DISLIKE_PERCENTAGE.getBoolean());
|
||||||
percentagePreference.setTitle(str("revanced_ryd_dislike_percentage_title"));
|
percentagePreference.setTitle(str("revanced_ryd_dislike_percentage_title"));
|
||||||
percentagePreference.setSummaryOn(str("revanced_ryd_dislike_percentage_summary_on"));
|
percentagePreference.setSummaryOn(str("revanced_ryd_dislike_percentage_summary_on"));
|
||||||
percentagePreference.setSummaryOff(str("revanced_ryd_dislike_percentage_summary_off"));
|
percentagePreference.setSummaryOff(str("revanced_ryd_dislike_percentage_summary_off"));
|
||||||
percentagePreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
percentagePreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||||
SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.saveValue(newValue);
|
SettingsEnum.RYD_DISLIKE_PERCENTAGE.saveValue(newValue);
|
||||||
ReturnYouTubeDislike.clearCache();
|
ReturnYouTubeDislike.clearCache();
|
||||||
updateUIState();
|
updateUIState();
|
||||||
return true;
|
return true;
|
||||||
@ -72,18 +96,30 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
|||||||
preferenceScreen.addPreference(percentagePreference);
|
preferenceScreen.addPreference(percentagePreference);
|
||||||
|
|
||||||
compactLayoutPreference = new SwitchPreference(context);
|
compactLayoutPreference = new SwitchPreference(context);
|
||||||
compactLayoutPreference.setChecked(SettingsEnum.RYD_USE_COMPACT_LAYOUT.getBoolean());
|
compactLayoutPreference.setChecked(SettingsEnum.RYD_COMPACT_LAYOUT.getBoolean());
|
||||||
compactLayoutPreference.setTitle(str("revanced_ryd_compact_layout_title"));
|
compactLayoutPreference.setTitle(str("revanced_ryd_compact_layout_title"));
|
||||||
compactLayoutPreference.setSummaryOn(str("revanced_ryd_compact_layout_summary_on"));
|
compactLayoutPreference.setSummaryOn(str("revanced_ryd_compact_layout_summary_on"));
|
||||||
compactLayoutPreference.setSummaryOff(str("revanced_ryd_compact_layout_summary_off"));
|
compactLayoutPreference.setSummaryOff(str("revanced_ryd_compact_layout_summary_off"));
|
||||||
compactLayoutPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
compactLayoutPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||||
SettingsEnum.RYD_USE_COMPACT_LAYOUT.saveValue(newValue);
|
SettingsEnum.RYD_COMPACT_LAYOUT.saveValue(newValue);
|
||||||
ReturnYouTubeDislike.clearCache();
|
ReturnYouTubeDislike.clearCache();
|
||||||
updateUIState();
|
updateUIState();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
preferenceScreen.addPreference(compactLayoutPreference);
|
preferenceScreen.addPreference(compactLayoutPreference);
|
||||||
|
|
||||||
|
toastOnRYDNotAvailable = new SwitchPreference(context);
|
||||||
|
toastOnRYDNotAvailable.setChecked(SettingsEnum.RYD_TOAST_ON_CONNECTION_ERROR.getBoolean());
|
||||||
|
toastOnRYDNotAvailable.setTitle(str("ryd_toast_on_connection_error_title"));
|
||||||
|
toastOnRYDNotAvailable.setSummaryOn(str("ryd_toast_on_connection_error_summary_on"));
|
||||||
|
toastOnRYDNotAvailable.setSummaryOff(str("ryd_toast_on_connection_error_summary_off"));
|
||||||
|
toastOnRYDNotAvailable.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||||
|
SettingsEnum.RYD_TOAST_ON_CONNECTION_ERROR.saveValue(newValue);
|
||||||
|
updateUIState();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
preferenceScreen.addPreference(toastOnRYDNotAvailable);
|
||||||
|
|
||||||
updateUIState();
|
updateUIState();
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import android.preference.PreferenceScreen;
|
|||||||
import android.preference.SwitchPreference;
|
import android.preference.SwitchPreference;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.util.TypedValue;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -51,6 +52,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
private SwitchPreference showSkipToast;
|
private SwitchPreference showSkipToast;
|
||||||
private SwitchPreference trackSkips;
|
private SwitchPreference trackSkips;
|
||||||
private SwitchPreference showTimeWithoutSegments;
|
private SwitchPreference showTimeWithoutSegments;
|
||||||
|
private SwitchPreference toastOnConnectionError;
|
||||||
|
|
||||||
private EditTextPreference newSegmentStep;
|
private EditTextPreference newSegmentStep;
|
||||||
private EditTextPreference minSegmentDuration;
|
private EditTextPreference minSegmentDuration;
|
||||||
@ -67,41 +69,44 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
SponsorBlockViewController.hideAll();
|
SponsorBlockViewController.hideAll();
|
||||||
SegmentPlaybackController.setCurrentVideoId(null);
|
SegmentPlaybackController.setCurrentVideoId(null);
|
||||||
} else if (!SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.getBoolean()) {
|
} else if (!SettingsEnum.SB_CREATE_NEW_SEGMENT.getBoolean()) {
|
||||||
SponsorBlockViewController.hideNewSegmentLayout();
|
SponsorBlockViewController.hideNewSegmentLayout();
|
||||||
}
|
}
|
||||||
// voting and add new segment buttons automatically shows/hides themselves
|
// voting and add new segment buttons automatically shows/hides themselves
|
||||||
|
|
||||||
sbEnabled.setChecked(enabled);
|
sbEnabled.setChecked(enabled);
|
||||||
|
|
||||||
addNewSegment.setChecked(SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.getBoolean());
|
addNewSegment.setChecked(SettingsEnum.SB_CREATE_NEW_SEGMENT.getBoolean());
|
||||||
addNewSegment.setEnabled(enabled);
|
addNewSegment.setEnabled(enabled);
|
||||||
|
|
||||||
votingEnabled.setChecked(SettingsEnum.SB_VOTING_ENABLED.getBoolean());
|
votingEnabled.setChecked(SettingsEnum.SB_VOTING_BUTTON.getBoolean());
|
||||||
votingEnabled.setEnabled(enabled);
|
votingEnabled.setEnabled(enabled);
|
||||||
|
|
||||||
compactSkipButton.setChecked(SettingsEnum.SB_USE_COMPACT_SKIP_BUTTON.getBoolean());
|
compactSkipButton.setChecked(SettingsEnum.SB_COMPACT_SKIP_BUTTON.getBoolean());
|
||||||
compactSkipButton.setEnabled(enabled);
|
compactSkipButton.setEnabled(enabled);
|
||||||
|
|
||||||
autoHideSkipSegmentButton.setChecked(SettingsEnum.SB_AUTO_HIDE_SKIP_BUTTON.getBoolean());
|
autoHideSkipSegmentButton.setChecked(SettingsEnum.SB_AUTO_HIDE_SKIP_BUTTON.getBoolean());
|
||||||
autoHideSkipSegmentButton.setEnabled(enabled);
|
autoHideSkipSegmentButton.setEnabled(enabled);
|
||||||
|
|
||||||
showSkipToast.setChecked(SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean());
|
showSkipToast.setChecked(SettingsEnum.SB_TOAST_ON_SKIP.getBoolean());
|
||||||
showSkipToast.setEnabled(enabled);
|
showSkipToast.setEnabled(enabled);
|
||||||
|
|
||||||
|
toastOnConnectionError.setChecked(SettingsEnum.SB_TOAST_ON_CONNECTION_ERROR.getBoolean());
|
||||||
|
toastOnConnectionError.setEnabled(enabled);
|
||||||
|
|
||||||
trackSkips.setChecked(SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean());
|
trackSkips.setChecked(SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean());
|
||||||
trackSkips.setEnabled(enabled);
|
trackSkips.setEnabled(enabled);
|
||||||
|
|
||||||
showTimeWithoutSegments.setChecked(SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.getBoolean());
|
showTimeWithoutSegments.setChecked(SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.getBoolean());
|
||||||
showTimeWithoutSegments.setEnabled(enabled);
|
showTimeWithoutSegments.setEnabled(enabled);
|
||||||
|
|
||||||
newSegmentStep.setText(SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getObjectValue().toString());
|
newSegmentStep.setText(SettingsEnum.SB_CREATE_NEW_SEGMENT_STEP.getObjectValue().toString());
|
||||||
newSegmentStep.setEnabled(enabled);
|
newSegmentStep.setEnabled(enabled);
|
||||||
|
|
||||||
minSegmentDuration.setText(SettingsEnum.SB_MIN_DURATION.getObjectValue().toString());
|
minSegmentDuration.setText(SettingsEnum.SB_SEGMENT_MIN_DURATION.getObjectValue().toString());
|
||||||
minSegmentDuration.setEnabled(enabled);
|
minSegmentDuration.setEnabled(enabled);
|
||||||
|
|
||||||
privateUserId.setText(SettingsEnum.SB_UUID.getString());
|
privateUserId.setText(SettingsEnum.SB_PRIVATE_USER_ID.getString());
|
||||||
privateUserId.setEnabled(enabled);
|
privateUserId.setEnabled(enabled);
|
||||||
|
|
||||||
apiUrl.setEnabled(enabled);
|
apiUrl.setEnabled(enabled);
|
||||||
@ -171,7 +176,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
votingEnabled.setSummaryOff(str("sb_enable_voting_sum_off"));
|
votingEnabled.setSummaryOff(str("sb_enable_voting_sum_off"));
|
||||||
category.addPreference(votingEnabled);
|
category.addPreference(votingEnabled);
|
||||||
votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
|
votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
SettingsEnum.SB_VOTING_ENABLED.saveValue(newValue);
|
SettingsEnum.SB_VOTING_BUTTON.saveValue(newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -182,7 +187,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
compactSkipButton.setSummaryOff(str("sb_enable_compact_skip_button_sum_off"));
|
compactSkipButton.setSummaryOff(str("sb_enable_compact_skip_button_sum_off"));
|
||||||
category.addPreference(compactSkipButton);
|
category.addPreference(compactSkipButton);
|
||||||
compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
SettingsEnum.SB_USE_COMPACT_SKIP_BUTTON.saveValue(newValue);
|
SettingsEnum.SB_COMPACT_SKIP_BUTTON.saveValue(newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -207,7 +212,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
|
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
SettingsEnum.SB_SHOW_TOAST_ON_SKIP.saveValue(newValue);
|
SettingsEnum.SB_TOAST_ON_SKIP.saveValue(newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -218,7 +223,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
showTimeWithoutSegments.setSummaryOn(str("sb_general_time_without_sum_on"));
|
showTimeWithoutSegments.setSummaryOn(str("sb_general_time_without_sum_on"));
|
||||||
showTimeWithoutSegments.setSummaryOff(str("sb_general_time_without_sum_off"));
|
showTimeWithoutSegments.setSummaryOff(str("sb_general_time_without_sum_off"));
|
||||||
showTimeWithoutSegments.setOnPreferenceChangeListener((preference1, newValue) -> {
|
showTimeWithoutSegments.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.saveValue(newValue);
|
SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.saveValue(newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -247,7 +252,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.saveValue(newValue);
|
SettingsEnum.SB_CREATE_NEW_SEGMENT.saveValue(newValue);
|
||||||
updateUI();
|
updateUI();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -262,7 +267,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
ReVancedUtils.showToastLong(str("sb_general_adjusting_invalid"));
|
ReVancedUtils.showToastLong(str("sb_general_adjusting_invalid"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.saveValue(newAdjustmentValue);
|
SettingsEnum.SB_CREATE_NEW_SEGMENT_STEP.saveValue(newAdjustmentValue);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
category.addPreference(newSegmentStep);
|
category.addPreference(newSegmentStep);
|
||||||
@ -282,6 +287,17 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
screen.addPreference(category);
|
screen.addPreference(category);
|
||||||
category.setTitle(str("sb_general"));
|
category.setTitle(str("sb_general"));
|
||||||
|
|
||||||
|
toastOnConnectionError = new SwitchPreference(context);
|
||||||
|
toastOnConnectionError.setTitle(str("sb_toast_on_connection_error_title"));
|
||||||
|
toastOnConnectionError.setSummaryOn(str("sb_toast_on_connection_error_summary_on"));
|
||||||
|
toastOnConnectionError.setSummaryOff(str("sb_toast_on_connection_error_summary_off"));
|
||||||
|
toastOnConnectionError.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
|
SettingsEnum.SB_TOAST_ON_CONNECTION_ERROR.saveValue(newValue);
|
||||||
|
updateUI();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
category.addPreference(toastOnConnectionError);
|
||||||
|
|
||||||
trackSkips = new SwitchPreference(context);
|
trackSkips = new SwitchPreference(context);
|
||||||
trackSkips.setTitle(str("sb_general_skipcount"));
|
trackSkips.setTitle(str("sb_general_skipcount"));
|
||||||
trackSkips.setSummaryOn(str("sb_general_skipcount_sum_on"));
|
trackSkips.setSummaryOn(str("sb_general_skipcount_sum_on"));
|
||||||
@ -298,7 +314,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
minSegmentDuration.setSummary(str("sb_general_min_duration_sum"));
|
minSegmentDuration.setSummary(str("sb_general_min_duration_sum"));
|
||||||
minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||||
minSegmentDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
minSegmentDuration.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||||
SettingsEnum.SB_MIN_DURATION.saveValue(Float.valueOf(newValue.toString()));
|
SettingsEnum.SB_SEGMENT_MIN_DURATION.saveValue(Float.valueOf(newValue.toString()));
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
category.addPreference(minSegmentDuration);
|
category.addPreference(minSegmentDuration);
|
||||||
@ -312,7 +328,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
ReVancedUtils.showToastLong(str("sb_general_uuid_invalid"));
|
ReVancedUtils.showToastLong(str("sb_general_uuid_invalid"));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SettingsEnum.SB_UUID.saveValue(newUUID);
|
SettingsEnum.SB_PRIVATE_USER_ID.saveValue(newUUID);
|
||||||
fetchAndDisplayStats();
|
fetchAndDisplayStats();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@ -351,9 +367,22 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
});
|
});
|
||||||
category.addPreference(apiUrl);
|
category.addPreference(apiUrl);
|
||||||
|
|
||||||
importExport = new EditTextPreference(context);
|
importExport = new EditTextPreference(context) {
|
||||||
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
|
builder.setNeutralButton(str("sb_settings_copy"), (dialog, which) -> {
|
||||||
|
ReVancedUtils.setClipboard(getEditText().getText().toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
importExport.setTitle(str("sb_settings_ie"));
|
importExport.setTitle(str("sb_settings_ie"));
|
||||||
importExport.setSummary(str("sb_settings_ie_sum"));
|
importExport.setSummary(str("sb_settings_ie_sum"));
|
||||||
|
importExport.getEditText().setInputType(InputType.TYPE_CLASS_TEXT
|
||||||
|
| InputType.TYPE_TEXT_FLAG_MULTI_LINE
|
||||||
|
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
importExport.getEditText().setAutofillHints((String) null);
|
||||||
|
}
|
||||||
|
importExport.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_PT, 8);
|
||||||
importExport.setOnPreferenceClickListener(preference1 -> {
|
importExport.setOnPreferenceClickListener(preference1 -> {
|
||||||
importExport.getEditText().setText(SponsorBlockSettings.exportSettings());
|
importExport.getEditText().setText(SponsorBlockSettings.exportSettings());
|
||||||
return true;
|
return true;
|
||||||
@ -419,6 +448,12 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
private void fetchAndDisplayStats() {
|
private void fetchAndDisplayStats() {
|
||||||
try {
|
try {
|
||||||
statsCategory.removeAll();
|
statsCategory.removeAll();
|
||||||
|
if (!SponsorBlockSettings.userHasSBPrivateId()) {
|
||||||
|
// User has never voted or created any segments. No stats to show.
|
||||||
|
addLocalUserStats();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Preference loadingPlaceholderPreference = new Preference(this.getActivity());
|
Preference loadingPlaceholderPreference = new Preference(this.getActivity());
|
||||||
loadingPlaceholderPreference.setEnabled(false);
|
loadingPlaceholderPreference.setEnabled(false);
|
||||||
statsCategory.addPreference(loadingPlaceholderPreference);
|
statsCategory.addPreference(loadingPlaceholderPreference);
|
||||||
@ -428,6 +463,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
UserStats stats = SBRequester.retrieveUserStats();
|
UserStats stats = SBRequester.retrieveUserStats();
|
||||||
ReVancedUtils.runOnMainThread(() -> { // get back on main thread to modify UI elements
|
ReVancedUtils.runOnMainThread(() -> { // get back on main thread to modify UI elements
|
||||||
addUserStats(loadingPlaceholderPreference, stats);
|
addUserStats(loadingPlaceholderPreference, stats);
|
||||||
|
addLocalUserStats();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -450,7 +486,8 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
statsCategory.removeAll();
|
statsCategory.removeAll();
|
||||||
Context context = statsCategory.getContext();
|
Context context = statsCategory.getContext();
|
||||||
|
|
||||||
{
|
if (stats.totalSegmentCountIncludingIgnored > 0) {
|
||||||
|
// If user has not created any segments, there's no reason to set a username.
|
||||||
EditTextPreference preference = new EditTextPreference(context);
|
EditTextPreference preference = new EditTextPreference(context);
|
||||||
statsCategory.addPreference(preference);
|
statsCategory.addPreference(preference);
|
||||||
String userName = stats.userName;
|
String userName = stats.userName;
|
||||||
@ -482,7 +519,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
statsCategory.addPreference(preference);
|
statsCategory.addPreference(preference);
|
||||||
String formatted = statsNumberOfSegmentsSkippedFormatter.format(stats.segmentCount);
|
String formatted = statsNumberOfSegmentsSkippedFormatter.format(stats.segmentCount);
|
||||||
preference.setTitle(fromHtml(str("sb_stats_submissions", formatted)));
|
preference.setTitle(fromHtml(str("sb_stats_submissions", formatted)));
|
||||||
if (stats.segmentCount == 0) {
|
if (stats.totalSegmentCountIncludingIgnored == 0) {
|
||||||
preference.setSelectable(false);
|
preference.setSelectable(false);
|
||||||
} else {
|
} else {
|
||||||
preference.setOnPreferenceClickListener(preference1 -> {
|
preference.setOnPreferenceClickListener(preference1 -> {
|
||||||
@ -512,7 +549,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
|
|
||||||
String stats_saved;
|
String stats_saved;
|
||||||
String stats_saved_sum;
|
String stats_saved_sum;
|
||||||
if (stats.segmentCount == 0) {
|
if (stats.totalSegmentCountIncludingIgnored == 0) {
|
||||||
stats_saved = str("sb_stats_saved_zero");
|
stats_saved = str("sb_stats_saved_zero");
|
||||||
stats_saved_sum = str("sb_stats_saved_sum_zero");
|
stats_saved_sum = str("sb_stats_saved_sum_zero");
|
||||||
} else {
|
} else {
|
||||||
@ -528,34 +565,34 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
|
||||||
// time the user saved by using SB
|
|
||||||
Preference preference = new Preference(context);
|
|
||||||
statsCategory.addPreference(preference);
|
|
||||||
|
|
||||||
Runnable updateStatsSelfSaved = () -> {
|
|
||||||
String formatted = statsNumberOfSegmentsSkippedFormatter.format(SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.getInt());
|
|
||||||
preference.setTitle(fromHtml(str("sb_stats_self_saved", formatted)));
|
|
||||||
String formattedSaved = SponsorBlockUtils.getTimeSavedString(SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.getLong() / 1000);
|
|
||||||
preference.setSummary(fromHtml(str("sb_stats_self_saved_sum", formattedSaved)));
|
|
||||||
};
|
|
||||||
updateStatsSelfSaved.run();
|
|
||||||
preference.setOnPreferenceClickListener(preference1 -> {
|
|
||||||
new AlertDialog.Builder(preference1.getContext())
|
|
||||||
.setTitle(str("sb_stats_self_saved_reset_title"))
|
|
||||||
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
|
||||||
SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.saveValue(SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.defaultValue);
|
|
||||||
SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.saveValue(SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.defaultValue);
|
|
||||||
updateStatsSelfSaved.run();
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.no, null).show();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "fetchAndDisplayStats failure", ex);
|
LogHelper.printException(() -> "addUserStats failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addLocalUserStats() {
|
||||||
|
// time the user saved by using SB
|
||||||
|
Preference preference = new Preference(statsCategory.getContext());
|
||||||
|
statsCategory.addPreference(preference);
|
||||||
|
|
||||||
|
Runnable updateStatsSelfSaved = () -> {
|
||||||
|
String formatted = statsNumberOfSegmentsSkippedFormatter.format(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt());
|
||||||
|
preference.setTitle(fromHtml(str("sb_stats_self_saved", formatted)));
|
||||||
|
String formattedSaved = SponsorBlockUtils.getTimeSavedString(SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() / 1000);
|
||||||
|
preference.setSummary(fromHtml(str("sb_stats_self_saved_sum", formattedSaved)));
|
||||||
|
};
|
||||||
|
updateStatsSelfSaved.run();
|
||||||
|
preference.setOnPreferenceClickListener(preference1 -> {
|
||||||
|
new AlertDialog.Builder(preference1.getContext())
|
||||||
|
.setTitle(str("sb_stats_self_saved_reset_title"))
|
||||||
|
.setPositiveButton(android.R.string.yes, (dialog, whichButton) -> {
|
||||||
|
SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.saveValue(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.defaultValue);
|
||||||
|
SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.saveValue(SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.defaultValue);
|
||||||
|
updateStatsSelfSaved.run();
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.no, null).show();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,63 @@
|
|||||||
package app.revanced.integrations.shared
|
package app.revanced.integrations.shared
|
||||||
|
|
||||||
import app.revanced.integrations.utils.Event
|
import app.revanced.integrations.utils.Event
|
||||||
|
import app.revanced.integrations.utils.LogHelper
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WatchWhile player type
|
* WatchWhile player type
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
|
||||||
enum class PlayerType {
|
enum class PlayerType {
|
||||||
NONE, // includes Shorts and Stories playback
|
/**
|
||||||
HIDDEN, // A Shorts or Stories, if a regular video is minimized and a Short/Story is then opened
|
* Includes Shorts and Stories playback.
|
||||||
|
*/
|
||||||
|
NONE,
|
||||||
|
/**
|
||||||
|
* A Shorts or Stories, if a regular video is minimized and a Short/Story is then opened.
|
||||||
|
*/
|
||||||
|
HIDDEN,
|
||||||
|
/**
|
||||||
|
* When spoofing to an old version of YouTube, and watching a short with a regular video in the background,
|
||||||
|
* the type will be this (and not [HIDDEN]).
|
||||||
|
*/
|
||||||
WATCH_WHILE_MINIMIZED,
|
WATCH_WHILE_MINIMIZED,
|
||||||
WATCH_WHILE_MAXIMIZED,
|
WATCH_WHILE_MAXIMIZED,
|
||||||
WATCH_WHILE_FULLSCREEN,
|
WATCH_WHILE_FULLSCREEN,
|
||||||
WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN,
|
WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN,
|
||||||
WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED,
|
WATCH_WHILE_SLIDING_MINIMIZED_MAXIMIZED,
|
||||||
|
/**
|
||||||
|
* When opening a short while a regular video is minimized, the type can momentarily be this.
|
||||||
|
*/
|
||||||
WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED,
|
WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED,
|
||||||
WATCH_WHILE_SLIDING_FULLSCREEN_DISMISSED,
|
WATCH_WHILE_SLIDING_FULLSCREEN_DISMISSED,
|
||||||
INLINE_MINIMAL, // home feed video playback
|
/**
|
||||||
|
* Home feed video playback.
|
||||||
|
*/
|
||||||
|
INLINE_MINIMAL,
|
||||||
VIRTUAL_REALITY_FULLSCREEN,
|
VIRTUAL_REALITY_FULLSCREEN,
|
||||||
WATCH_WHILE_PICTURE_IN_PICTURE;
|
WATCH_WHILE_PICTURE_IN_PICTURE;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
|
||||||
* safely parse from a string
|
private val nameToPlayerType = values().associateBy { it.name }
|
||||||
*
|
|
||||||
* @param name the name to find
|
|
||||||
* @return the enum constant, or null if not found
|
|
||||||
*/
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun safeParseFromString(name: String): PlayerType? {
|
fun setFromString(enumName: String) {
|
||||||
return values().firstOrNull { it.name == name }
|
val newType = nameToPlayerType[enumName]
|
||||||
|
if (newType == null) {
|
||||||
|
LogHelper.printException { "Unknown PlayerType encountered: $enumName" }
|
||||||
|
} else if (current != newType) {
|
||||||
|
LogHelper.printDebug { "PlayerType changed to: $newType" }
|
||||||
|
current = newType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the current player type, as reported by [app.revanced.integrations.patches.PlayerTypeHookPatch.YouTubePlayerOverlaysLayout_updatePlayerTypeHookEX]
|
* The current player type.
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
var current
|
var current
|
||||||
get() = currentPlayerType
|
get() = currentPlayerType
|
||||||
set(value) {
|
private set(value) {
|
||||||
currentPlayerType = value
|
currentPlayerType = value
|
||||||
onChange(currentPlayerType)
|
onChange(currentPlayerType)
|
||||||
}
|
}
|
||||||
@ -53,11 +72,30 @@ enum class PlayerType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the current player type is [NONE] or [HIDDEN]
|
* Check if the current player type is [NONE] or [HIDDEN].
|
||||||
|
* Useful to check if a short is currently playing.
|
||||||
*
|
*
|
||||||
* @return True, if nothing, a Short, or a Story is playing.
|
* Does not include the first moment after a short is opened when a regular video is minimized on screen,
|
||||||
|
* or while watching a short with a regular video present on a spoofed old version of YouTube.
|
||||||
|
* To include those situations instead use [isNoneHiddenOrMinimized].
|
||||||
*/
|
*/
|
||||||
fun isNoneOrHidden(): Boolean {
|
fun isNoneOrHidden(): Boolean {
|
||||||
return this == NONE || this == HIDDEN
|
return this == NONE || this == HIDDEN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the current player type is [NONE], [HIDDEN], [WATCH_WHILE_MINIMIZED], [WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED].
|
||||||
|
*
|
||||||
|
* Useful to check if a Short is being played,
|
||||||
|
* although can return false positive if the player is minimized.
|
||||||
|
*
|
||||||
|
* @return If nothing, a Short, a Story,
|
||||||
|
* or a regular video is minimized video or sliding off screen to a dismissed or hidden state.
|
||||||
|
*/
|
||||||
|
fun isNoneHiddenOrMinimized(): Boolean {
|
||||||
|
return this == NONE || this == HIDDEN
|
||||||
|
|| this == WATCH_WHILE_MINIMIZED
|
||||||
|
|| this == WATCH_WHILE_SLIDING_MINIMIZED_DISMISSED
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package app.revanced.integrations.shared
|
||||||
|
|
||||||
|
import app.revanced.integrations.utils.LogHelper
|
||||||
|
import app.revanced.integrations.patches.VideoInformation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VideoState playback state.
|
||||||
|
*/
|
||||||
|
enum class VideoState {
|
||||||
|
NEW,
|
||||||
|
PLAYING,
|
||||||
|
PAUSED,
|
||||||
|
RECOVERABLE_ERROR,
|
||||||
|
UNRECOVERABLE_ERROR,
|
||||||
|
/**
|
||||||
|
* @see [VideoInformation.isAtEndOfVideo]
|
||||||
|
*/
|
||||||
|
ENDED;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val nameToVideoState = values().associateBy { it.name }
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun setFromString(enumName: String) {
|
||||||
|
val state = nameToVideoState[enumName]
|
||||||
|
if (state == null) {
|
||||||
|
LogHelper.printException { "Unknown VideoState encountered: $enumName" }
|
||||||
|
} else if (currentVideoState != state) {
|
||||||
|
LogHelper.printDebug { "VideoState changed to: $state" }
|
||||||
|
currentVideoState = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depending on which hook this is called from,
|
||||||
|
* this value may not be up to date with the actual playback state.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
var current: VideoState?
|
||||||
|
get() = currentVideoState
|
||||||
|
private set(value) {
|
||||||
|
currentVideoState = value
|
||||||
|
}
|
||||||
|
|
||||||
|
private var currentVideoState : VideoState? = null
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,15 @@
|
|||||||
package app.revanced.integrations.sponsorblock;
|
package app.revanced.integrations.sponsorblock;
|
||||||
|
|
||||||
import static app.revanced.integrations.utils.StringRef.str;
|
|
||||||
|
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import app.revanced.integrations.patches.VideoInformation;
|
import app.revanced.integrations.patches.VideoInformation;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.shared.PlayerType;
|
import app.revanced.integrations.shared.PlayerType;
|
||||||
|
import app.revanced.integrations.shared.VideoState;
|
||||||
import app.revanced.integrations.sponsorblock.objects.CategoryBehaviour;
|
import app.revanced.integrations.sponsorblock.objects.CategoryBehaviour;
|
||||||
import app.revanced.integrations.sponsorblock.objects.SegmentCategory;
|
import app.revanced.integrations.sponsorblock.objects.SegmentCategory;
|
||||||
import app.revanced.integrations.sponsorblock.objects.SponsorSegment;
|
import app.revanced.integrations.sponsorblock.objects.SponsorSegment;
|
||||||
@ -28,6 +18,11 @@ import app.revanced.integrations.sponsorblock.ui.SponsorBlockViewController;
|
|||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.utils.StringRef.str;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles showing, scheduling, and skipping of all {@link SponsorSegment} for the current video.
|
* Handles showing, scheduling, and skipping of all {@link SponsorSegment} for the current video.
|
||||||
*
|
*
|
||||||
@ -38,12 +33,12 @@ public class SegmentPlaybackController {
|
|||||||
* Length of time to show a skip button for a highlight segment,
|
* Length of time to show a skip button for a highlight segment,
|
||||||
* or a regular segment if {@link SettingsEnum#SB_AUTO_HIDE_SKIP_BUTTON} is enabled.
|
* or a regular segment if {@link SettingsEnum#SB_AUTO_HIDE_SKIP_BUTTON} is enabled.
|
||||||
*
|
*
|
||||||
* Because Effectively, this value is rounded up to the next second.
|
* Effectively this value is rounded up to the next second.
|
||||||
*/
|
*/
|
||||||
private static final long DURATION_TO_SHOW_SKIP_BUTTON = 3800;
|
private static final long DURATION_TO_SHOW_SKIP_BUTTON = 3800;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Highlight segments have zero length, as they are a point in time.
|
* Highlight segments have zero length as they are a point in time.
|
||||||
* Draw them on screen using a fixed width bar.
|
* Draw them on screen using a fixed width bar.
|
||||||
* Value is independent of device dpi.
|
* Value is independent of device dpi.
|
||||||
*/
|
*/
|
||||||
@ -102,9 +97,9 @@ public class SegmentPlaybackController {
|
|||||||
@Nullable
|
@Nullable
|
||||||
private static String timeWithoutSegments;
|
private static String timeWithoutSegments;
|
||||||
|
|
||||||
private static float sponsorBarLeft = 1f;
|
private static int sponsorBarAbsoluteLeft;
|
||||||
private static float sponsorBarRight = 1f;
|
private static int sponsorAbsoluteBarRight;
|
||||||
private static float sponsorBarThickness = 2f;
|
private static int sponsorBarThickness;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
static SponsorSegment[] getSegments() {
|
static SponsorSegment[] getSegments() {
|
||||||
@ -177,7 +172,7 @@ public class SegmentPlaybackController {
|
|||||||
* Injection point.
|
* Injection point.
|
||||||
* Initializes SponsorBlock when the video player starts playing a new video.
|
* Initializes SponsorBlock when the video player starts playing a new video.
|
||||||
*/
|
*/
|
||||||
public static void initialize(Object _o) {
|
public static void initialize(Object ignoredPlayerController) {
|
||||||
try {
|
try {
|
||||||
ReVancedUtils.verifyOnMainThread();
|
ReVancedUtils.verifyOnMainThread();
|
||||||
SponsorBlockSettings.initialize();
|
SponsorBlockSettings.initialize();
|
||||||
@ -235,7 +230,7 @@ public class SegmentPlaybackController {
|
|||||||
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
||||||
|
|
||||||
ReVancedUtils.runOnMainThread(()-> {
|
ReVancedUtils.runOnMainThread(()-> {
|
||||||
if (!videoId.equals(SegmentPlaybackController.currentVideoId)) {
|
if (!videoId.equals(currentVideoId)) {
|
||||||
// user changed videos before get segments network call could complete
|
// user changed videos before get segments network call could complete
|
||||||
LogHelper.printDebug(() -> "Ignoring segments for prior video: " + videoId);
|
LogHelper.printDebug(() -> "Ignoring segments for prior video: " + videoId);
|
||||||
return;
|
return;
|
||||||
@ -522,17 +517,21 @@ public class SegmentPlaybackController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean videoIsPaused = VideoState.getCurrent() == VideoState.PAUSED;
|
||||||
if (!userManuallySkipped) {
|
if (!userManuallySkipped) {
|
||||||
// check for any smaller embedded segments, and count those as autoskipped
|
// check for any smaller embedded segments, and count those as autoskipped
|
||||||
final boolean showSkipToast = SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean();
|
final boolean showSkipToast = SettingsEnum.SB_TOAST_ON_SKIP.getBoolean();
|
||||||
for (final SponsorSegment otherSegment : segments) {
|
for (final SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
|
||||||
if (segmentToSkip.end < otherSegment.start) {
|
if (segmentToSkip.end < otherSegment.start) {
|
||||||
break; // no other segments can be contained
|
break; // no other segments can be contained
|
||||||
}
|
}
|
||||||
if (otherSegment == segmentToSkip ||
|
if (otherSegment == segmentToSkip ||
|
||||||
(otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) {
|
(otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) {
|
||||||
otherSegment.didAutoSkipped = true;
|
otherSegment.didAutoSkipped = true;
|
||||||
if (showSkipToast) {
|
// Do not show a toast if the user is scrubbing thru a paused video.
|
||||||
|
// Cannot do this video state check in setTime or earlier in this method, as the video state may not be up to date.
|
||||||
|
// So instead, only hide toasts because all other skip logic done while paused causes no harm.
|
||||||
|
if (showSkipToast && !videoIsPaused) {
|
||||||
showSkippedSegmentToast(otherSegment);
|
showSkippedSegmentToast(otherSegment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -542,7 +541,7 @@ public class SegmentPlaybackController {
|
|||||||
if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
|
if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
|
||||||
removeUnsubmittedSegments();
|
removeUnsubmittedSegments();
|
||||||
SponsorBlockUtils.setNewSponsorSegmentPreviewed();
|
SponsorBlockUtils.setNewSponsorSegmentPreviewed();
|
||||||
} else {
|
} else if (!videoIsPaused) {
|
||||||
SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
|
SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@ -599,20 +598,6 @@ public class SegmentPlaybackController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static void setSponsorBarAbsoluteLeft(final Rect rect) {
|
|
||||||
setSponsorBarAbsoluteLeft(rect.left);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setSponsorBarAbsoluteLeft(final float left) {
|
|
||||||
if (sponsorBarLeft != left) {
|
|
||||||
LogHelper.printDebug(() -> String.format("setSponsorBarAbsoluteLeft: left=%.2f", left));
|
|
||||||
sponsorBarLeft = left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point
|
* Injection point
|
||||||
*/
|
*/
|
||||||
@ -620,42 +605,36 @@ public class SegmentPlaybackController {
|
|||||||
try {
|
try {
|
||||||
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
||||||
field.setAccessible(true);
|
field.setAccessible(true);
|
||||||
Rect rect = (Rect) field.get(self);
|
Rect rect = (Rect) Objects.requireNonNull(field.get(self));
|
||||||
if (rect == null) {
|
setSponsorBarAbsoluteLeft(rect);
|
||||||
LogHelper.printException(() -> "Could not find sponsorblock rect");
|
setSponsorBarAbsoluteRight(rect);
|
||||||
} else {
|
|
||||||
setSponsorBarAbsoluteLeft(rect.left);
|
|
||||||
setSponsorBarAbsoluteRight(rect.right);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "setSponsorBarRect failure", ex);
|
LogHelper.printException(() -> "setSponsorBarRect failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static void setSponsorBarAbsoluteLeft(Rect rect) {
|
||||||
* Injection point.
|
final int left = rect.left;
|
||||||
*/
|
if (sponsorBarAbsoluteLeft != left) {
|
||||||
public static void setSponsorBarAbsoluteRight(final Rect rect) {
|
LogHelper.printDebug(() -> "setSponsorBarAbsoluteLeft: " + left);
|
||||||
setSponsorBarAbsoluteRight(rect.right);
|
sponsorBarAbsoluteLeft = left;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setSponsorBarAbsoluteRight(final float right) {
|
private static void setSponsorBarAbsoluteRight(Rect rect) {
|
||||||
if (sponsorBarRight != right) {
|
final int right = rect.right;
|
||||||
LogHelper.printDebug(() -> String.format("setSponsorBarAbsoluteRight: right=%.2f", right));
|
if (sponsorAbsoluteBarRight != right) {
|
||||||
sponsorBarRight = right;
|
LogHelper.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
|
||||||
|
sponsorAbsoluteBarRight = right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point
|
* Injection point
|
||||||
*/
|
*/
|
||||||
public static void setSponsorBarThickness(final int thickness) {
|
public static void setSponsorBarThickness(int thickness) {
|
||||||
setSponsorBarThickness((float) thickness);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setSponsorBarThickness(final float thickness) {
|
|
||||||
if (sponsorBarThickness != thickness) {
|
if (sponsorBarThickness != thickness) {
|
||||||
LogHelper.printDebug(() -> String.format("setSponsorBarThickness: %.2f", thickness));
|
LogHelper.printDebug(() -> "setSponsorBarThickness: " + thickness);
|
||||||
sponsorBarThickness = thickness;
|
sponsorBarThickness = thickness;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -665,7 +644,7 @@ public class SegmentPlaybackController {
|
|||||||
*/
|
*/
|
||||||
public static String appendTimeWithoutSegments(String totalTime) {
|
public static String appendTimeWithoutSegments(String totalTime) {
|
||||||
try {
|
try {
|
||||||
if (SettingsEnum.SB_ENABLED.getBoolean() && SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.getBoolean()
|
if (SettingsEnum.SB_ENABLED.getBoolean() && SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.getBoolean()
|
||||||
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)) {
|
&& !TextUtils.isEmpty(totalTime) && !TextUtils.isEmpty(timeWithoutSegments)) {
|
||||||
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages
|
// Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages
|
||||||
return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override
|
return "\u202D" + totalTime + timeWithoutSegments; // u202D = left to right override
|
||||||
@ -679,7 +658,7 @@ public class SegmentPlaybackController {
|
|||||||
|
|
||||||
private static void calculateTimeWithoutSegments() {
|
private static void calculateTimeWithoutSegments() {
|
||||||
final long currentVideoLength = VideoInformation.getVideoLength();
|
final long currentVideoLength = VideoInformation.getVideoLength();
|
||||||
if (!SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.getBoolean() || currentVideoLength <= 0
|
if (!SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.getBoolean() || currentVideoLength <= 0
|
||||||
|| segments == null || segments.length == 0) {
|
|| segments == null || segments.length == 0) {
|
||||||
timeWithoutSegments = null;
|
timeWithoutSegments = null;
|
||||||
return;
|
return;
|
||||||
@ -736,25 +715,23 @@ public class SegmentPlaybackController {
|
|||||||
*/
|
*/
|
||||||
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
|
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
|
||||||
try {
|
try {
|
||||||
if (sponsorBarThickness < 0.1) return;
|
|
||||||
if (segments == null) return;
|
if (segments == null) return;
|
||||||
final long videoLength = VideoInformation.getVideoLength();
|
final long videoLength = VideoInformation.getVideoLength();
|
||||||
if (videoLength <= 0) return;
|
if (videoLength <= 0) return;
|
||||||
|
|
||||||
final float thicknessDiv2 = sponsorBarThickness / 2;
|
final int thicknessDiv2 = sponsorBarThickness / 2; // rounds down
|
||||||
final float top = posY - thicknessDiv2;
|
final float top = posY - (sponsorBarThickness - thicknessDiv2);
|
||||||
final float bottom = posY + thicknessDiv2;
|
final float bottom = posY + thicknessDiv2;
|
||||||
final float absoluteLeft = sponsorBarLeft;
|
final float videoMillisecondsToPixels = (1f / videoLength) * (sponsorAbsoluteBarRight - sponsorBarAbsoluteLeft);
|
||||||
final float absoluteRight = sponsorBarRight;
|
final float leftPadding = sponsorBarAbsoluteLeft;
|
||||||
|
|
||||||
final float tmp1 = (1f / videoLength) * (absoluteRight - absoluteLeft);
|
|
||||||
for (SponsorSegment segment : segments) {
|
for (SponsorSegment segment : segments) {
|
||||||
final float left = segment.start * tmp1 + absoluteLeft;
|
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
|
||||||
final float right;
|
final float right;
|
||||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||||
right = left + getHighlightSegmentTimeBarScreenWidth();
|
right = left + getHighlightSegmentTimeBarScreenWidth();
|
||||||
} else {
|
} else {
|
||||||
right = segment.end * tmp1 + absoluteLeft;
|
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
||||||
}
|
}
|
||||||
canvas.drawRect(left, top, right, bottom, segment.category.paint);
|
canvas.drawRect(left, top, right, bottom, segment.category.paint);
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,13 @@ package app.revanced.integrations.sponsorblock;
|
|||||||
|
|
||||||
import static app.revanced.integrations.utils.StringRef.str;
|
import static app.revanced.integrations.utils.StringRef.str;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Patterns;
|
import android.util.Patterns;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@ -21,6 +24,10 @@ import app.revanced.integrations.utils.LogHelper;
|
|||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public class SponsorBlockSettings {
|
public class SponsorBlockSettings {
|
||||||
|
/**
|
||||||
|
* Minimum length a SB user id must be, as set by SB API.
|
||||||
|
*/
|
||||||
|
private static final int SB_PRIVATE_USER_ID_MINIMUM_LENGTH = 30;
|
||||||
|
|
||||||
public static void importSettings(@NonNull String json) {
|
public static void importSettings(@NonNull String json) {
|
||||||
ReVancedUtils.verifyOnMainThread();
|
ReVancedUtils.verifyOnMainThread();
|
||||||
@ -66,43 +73,43 @@ public class SponsorBlockSettings {
|
|||||||
}
|
}
|
||||||
editor.apply();
|
editor.apply();
|
||||||
|
|
||||||
String userID = settingsJson.getString("userID");
|
if (settingsJson.has("userID")) {
|
||||||
if (!isValidSBUserId(userID)) {
|
// User id does not exist if user never voted or created any segments.
|
||||||
throw new IllegalArgumentException("userId is blank");
|
String userID = settingsJson.getString("userID");
|
||||||
|
if (isValidSBUserId(userID)) {
|
||||||
|
SettingsEnum.SB_PRIVATE_USER_ID.saveValue(userID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
SettingsEnum.SB_UUID.saveValue(userID);
|
SettingsEnum.SB_USER_IS_VIP.saveValue(settingsJson.getBoolean("isVip"));
|
||||||
|
SettingsEnum.SB_TOAST_ON_SKIP.saveValue(!settingsJson.getBoolean("dontShowNotice"));
|
||||||
SettingsEnum.SB_IS_VIP.saveValue(settingsJson.getBoolean("isVip"));
|
|
||||||
SettingsEnum.SB_SHOW_TOAST_ON_SKIP.saveValue(!settingsJson.getBoolean("dontShowNotice"));
|
|
||||||
SettingsEnum.SB_TRACK_SKIP_COUNT.saveValue(settingsJson.getBoolean("trackViewCount"));
|
SettingsEnum.SB_TRACK_SKIP_COUNT.saveValue(settingsJson.getBoolean("trackViewCount"));
|
||||||
|
SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.saveValue(settingsJson.getBoolean("showTimeWithSkips"));
|
||||||
|
|
||||||
String serverAddress = settingsJson.getString("serverAddress");
|
String serverAddress = settingsJson.getString("serverAddress");
|
||||||
if (!isValidSBServerAddress(serverAddress)) {
|
if (isValidSBServerAddress(serverAddress)) { // Old versions of ReVanced exported wrong url format
|
||||||
throw new IllegalArgumentException(str("sb_api_url_invalid"));
|
SettingsEnum.SB_API_URL.saveValue(serverAddress);
|
||||||
}
|
}
|
||||||
SettingsEnum.SB_API_URL.saveValue(serverAddress);
|
|
||||||
|
|
||||||
SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.saveValue(settingsJson.getBoolean("showTimeWithSkips"));
|
final float minDuration = (float) settingsJson.getDouble("minDuration");
|
||||||
final float minDuration = (float)settingsJson.getDouble("minDuration");
|
|
||||||
if (minDuration < 0) {
|
if (minDuration < 0) {
|
||||||
throw new IllegalArgumentException("invalid minDuration: " + minDuration);
|
throw new IllegalArgumentException("invalid minDuration: " + minDuration);
|
||||||
}
|
}
|
||||||
SettingsEnum.SB_MIN_DURATION.saveValue(minDuration);
|
SettingsEnum.SB_SEGMENT_MIN_DURATION.saveValue(minDuration);
|
||||||
|
|
||||||
try {
|
if (settingsJson.has("skipCount")) { // Value not exported in old versions of ReVanced
|
||||||
int skipCount = settingsJson.getInt("skipCount");
|
int skipCount = settingsJson.getInt("skipCount");
|
||||||
if (skipCount < 0) {
|
if (skipCount < 0) {
|
||||||
throw new IllegalArgumentException("invalid skipCount: " + skipCount);
|
throw new IllegalArgumentException("invalid skipCount: " + skipCount);
|
||||||
}
|
}
|
||||||
SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.saveValue(skipCount);
|
SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.saveValue(skipCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settingsJson.has("minutesSaved")) {
|
||||||
final double minutesSaved = settingsJson.getDouble("minutesSaved");
|
final double minutesSaved = settingsJson.getDouble("minutesSaved");
|
||||||
if (minutesSaved < 0) {
|
if (minutesSaved < 0) {
|
||||||
throw new IllegalArgumentException("invalid minutesSaved: " + minutesSaved);
|
throw new IllegalArgumentException("invalid minutesSaved: " + minutesSaved);
|
||||||
}
|
}
|
||||||
SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.saveValue((long)(minutesSaved * 60 * 1000));
|
SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.saveValue((long) (minutesSaved * 60 * 1000));
|
||||||
} catch (JSONException ex) {
|
|
||||||
// ignore. values were not exported in prior versions of ReVanced
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReVancedUtils.showToastLong(str("sb_settings_import_successful"));
|
ReVancedUtils.showToastLong(str("sb_settings_import_successful"));
|
||||||
@ -136,15 +143,17 @@ public class SponsorBlockSettings {
|
|||||||
categorySelectionsArray.put(behaviorObject);
|
categorySelectionsArray.put(behaviorObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
json.put("userID", SettingsEnum.SB_UUID.getString());
|
if (SponsorBlockSettings.userHasSBPrivateId()) {
|
||||||
json.put("isVip", SettingsEnum.SB_IS_VIP.getBoolean());
|
json.put("userID", SettingsEnum.SB_PRIVATE_USER_ID.getString());
|
||||||
|
}
|
||||||
|
json.put("isVip", SettingsEnum.SB_USER_IS_VIP.getBoolean());
|
||||||
json.put("serverAddress", SettingsEnum.SB_API_URL.getString());
|
json.put("serverAddress", SettingsEnum.SB_API_URL.getString());
|
||||||
json.put("dontShowNotice", !SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean());
|
json.put("dontShowNotice", !SettingsEnum.SB_TOAST_ON_SKIP.getBoolean());
|
||||||
json.put("showTimeWithSkips", SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.getBoolean());
|
json.put("showTimeWithSkips", SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.getBoolean());
|
||||||
json.put("minDuration", SettingsEnum.SB_MIN_DURATION.getFloat());
|
json.put("minDuration", SettingsEnum.SB_SEGMENT_MIN_DURATION.getFloat());
|
||||||
json.put("trackViewCount", SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean());
|
json.put("trackViewCount", SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean());
|
||||||
json.put("skipCount", SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.getInt());
|
json.put("skipCount", SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt());
|
||||||
json.put("minutesSaved", SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.getLong() / (60f * 1000));
|
json.put("minutesSaved", SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() / (60f * 1000));
|
||||||
|
|
||||||
json.put("categorySelections", categorySelectionsArray);
|
json.put("categorySelections", categorySelectionsArray);
|
||||||
json.put("barTypes", barTypesObject);
|
json.put("barTypes", barTypesObject);
|
||||||
@ -152,13 +161,59 @@ public class SponsorBlockSettings {
|
|||||||
return json.toString(2);
|
return json.toString(2);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printInfo(() -> "failed to export settings", ex); // use info level, as we are showing our own toast
|
LogHelper.printInfo(() -> "failed to export settings", ex); // use info level, as we are showing our own toast
|
||||||
ReVancedUtils.showToastLong(str("sb_settings_export_failed"));
|
ReVancedUtils.showToastLong(str("sb_settings_export_failed", ex));
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export the categories using flatten json (no embedded dictionaries or arrays).
|
||||||
|
*/
|
||||||
|
public static void exportCategoriesToFlatJson(@Nullable Context dialogContext,
|
||||||
|
@NonNull JSONObject json) throws JSONException {
|
||||||
|
ReVancedUtils.verifyOnMainThread();
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
// If user has a SponsorBlock user id then show a warning.
|
||||||
|
if (dialogContext != null && SponsorBlockSettings.userHasSBPrivateId()
|
||||||
|
&& !SettingsEnum.SB_HIDE_EXPORT_WARNING.getBoolean()) {
|
||||||
|
new AlertDialog.Builder(dialogContext)
|
||||||
|
.setMessage(str("sb_settings_revanced_export_user_id_warning"))
|
||||||
|
.setNeutralButton(str("sb_settings_revanced_export_user_id_warning_dismiss"),
|
||||||
|
(dialog, which) -> SettingsEnum.SB_HIDE_EXPORT_WARNING.saveValue(true))
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
|
||||||
|
category.exportToFlatJSON(json);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import the categories using flatten json (no embedded dictionaries or arrays).
|
||||||
|
*
|
||||||
|
* @return the number of settings imported
|
||||||
|
*/
|
||||||
|
public static int importCategoriesFromFlatJson(JSONObject json) throws JSONException {
|
||||||
|
ReVancedUtils.verifyOnMainThread();
|
||||||
|
initialize();
|
||||||
|
|
||||||
|
int numberOfImportedSettings = 0;
|
||||||
|
SharedPreferences.Editor editor = SharedPrefCategory.SPONSOR_BLOCK.preferences.edit();
|
||||||
|
for (SegmentCategory category : SegmentCategory.categoriesWithoutUnsubmitted()) {
|
||||||
|
numberOfImportedSettings += category.importFromFlatJSON(json, editor);
|
||||||
|
}
|
||||||
|
editor.apply();
|
||||||
|
|
||||||
|
SegmentCategory.updateEnabledCategories();
|
||||||
|
|
||||||
|
return numberOfImportedSettings;
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isValidSBUserId(@NonNull String userId) {
|
public static boolean isValidSBUserId(@NonNull String userId) {
|
||||||
return !userId.isEmpty();
|
return !userId.isEmpty() && userId.length() >= SB_PRIVATE_USER_ID_MINIMUM_LENGTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -180,6 +235,29 @@ public class SponsorBlockSettings {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if the user has ever voted, created a segment, or imported existing SB settings.
|
||||||
|
*/
|
||||||
|
public static boolean userHasSBPrivateId() {
|
||||||
|
return !SettingsEnum.SB_PRIVATE_USER_ID.getString().isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this only if a user id is required (creating segments, voting).
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static String getSBPrivateUserID() {
|
||||||
|
String uuid = SettingsEnum.SB_PRIVATE_USER_ID.getString();
|
||||||
|
if (uuid.isEmpty()) {
|
||||||
|
uuid = (UUID.randomUUID().toString() +
|
||||||
|
UUID.randomUUID().toString() +
|
||||||
|
UUID.randomUUID().toString())
|
||||||
|
.replace("-", "");
|
||||||
|
SettingsEnum.SB_PRIVATE_USER_ID.saveValue(uuid);
|
||||||
|
}
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean initialized;
|
private static boolean initialized;
|
||||||
|
|
||||||
public static void initialize() {
|
public static void initialize() {
|
||||||
@ -188,15 +266,6 @@ public class SponsorBlockSettings {
|
|||||||
}
|
}
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
String uuid = SettingsEnum.SB_UUID.getString();
|
|
||||||
if (uuid.isEmpty()) {
|
|
||||||
uuid = (UUID.randomUUID().toString() +
|
|
||||||
UUID.randomUUID().toString() +
|
|
||||||
UUID.randomUUID().toString())
|
|
||||||
.replace("-", "");
|
|
||||||
SettingsEnum.SB_UUID.saveValue(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
SegmentCategory.loadFromPreferences();
|
SegmentCategory.loadFromPreferences();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,7 +172,7 @@ public class SponsorBlockUtils {
|
|||||||
for (int i = 0; i < voteOptions.length; i++) {
|
for (int i = 0; i < voteOptions.length; i++) {
|
||||||
SegmentVote voteOption = voteOptions[i];
|
SegmentVote voteOption = voteOptions[i];
|
||||||
String title = voteOption.title.toString();
|
String title = voteOption.title.toString();
|
||||||
if (SettingsEnum.SB_IS_VIP.getBoolean() && segment.isLocked && voteOption.shouldHighlight) {
|
if (SettingsEnum.SB_USER_IS_VIP.getBoolean() && segment.isLocked && voteOption.shouldHighlight) {
|
||||||
items[i] = Html.fromHtml(String.format("<font color=\"%s\">%s</font>", LOCKED_COLOR, title));
|
items[i] = Html.fromHtml(String.format("<font color=\"%s\">%s</font>", LOCKED_COLOR, title));
|
||||||
} else {
|
} else {
|
||||||
items[i] = title;
|
items[i] = title;
|
||||||
@ -214,20 +214,18 @@ public class SponsorBlockUtils {
|
|||||||
private static void submitNewSegment() {
|
private static void submitNewSegment() {
|
||||||
try {
|
try {
|
||||||
ReVancedUtils.verifyOnMainThread();
|
ReVancedUtils.verifyOnMainThread();
|
||||||
final String uuid = SettingsEnum.SB_UUID.getString();
|
|
||||||
final long start = newSponsorSegmentStartMillis;
|
final long start = newSponsorSegmentStartMillis;
|
||||||
final long end = newSponsorSegmentEndMillis;
|
final long end = newSponsorSegmentEndMillis;
|
||||||
final String videoId = VideoInformation.getVideoId();
|
final String videoId = VideoInformation.getVideoId();
|
||||||
final long videoLength = VideoInformation.getVideoLength();
|
final long videoLength = VideoInformation.getVideoLength();
|
||||||
final SegmentCategory segmentCategory = newUserCreatedSegmentCategory;
|
final SegmentCategory segmentCategory = newUserCreatedSegmentCategory;
|
||||||
if (start < 0 || end < 0 || start >= end || videoLength <= 0 || videoId.isEmpty()
|
if (start < 0 || end < 0 || start >= end || videoLength <= 0 || videoId.isEmpty() || segmentCategory == null) {
|
||||||
|| segmentCategory == null || uuid.isEmpty()) {
|
|
||||||
LogHelper.printException(() -> "invalid parameters");
|
LogHelper.printException(() -> "invalid parameters");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
clearUnsubmittedSegmentTimes();
|
clearUnsubmittedSegmentTimes();
|
||||||
ReVancedUtils.runOnBackgroundThread(() -> {
|
ReVancedUtils.runOnBackgroundThread(() -> {
|
||||||
SBRequester.submitSegments(uuid, videoId, segmentCategory.key, start, end, videoLength);
|
SBRequester.submitSegments(videoId, segmentCategory.key, start, end, videoLength);
|
||||||
SegmentPlaybackController.executeDownloadSegments(videoId);
|
SegmentPlaybackController.executeDownloadSegments(videoId);
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -380,9 +378,9 @@ public class SponsorBlockUtils {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
segment.recordedAsSkipped = true;
|
segment.recordedAsSkipped = true;
|
||||||
final long totalTimeSkipped = SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.getLong() + segment.length();
|
final long totalTimeSkipped = SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() + segment.length();
|
||||||
SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.saveValue(totalTimeSkipped);
|
SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.saveValue(totalTimeSkipped);
|
||||||
SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.saveValue(SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.getInt() + 1);
|
SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.saveValue(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt() + 1);
|
||||||
|
|
||||||
if (SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean()) {
|
if (SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean()) {
|
||||||
ReVancedUtils.runOnBackgroundThread(() -> SBRequester.sendSegmentSkippedViewedRequest(segment));
|
ReVancedUtils.runOnBackgroundThread(() -> SBRequester.sendSegmentSkippedViewedRequest(segment));
|
||||||
|
@ -16,6 +16,9 @@ import android.text.TextUtils;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -59,6 +62,11 @@ public enum SegmentCategory {
|
|||||||
private static final StringRef skipSponsorTextCompact = sf("sb_skip_button_compact");
|
private static final StringRef skipSponsorTextCompact = sf("sb_skip_button_compact");
|
||||||
private static final StringRef skipSponsorTextCompactHighlight = sf("sb_skip_button_compact_highlight");
|
private static final StringRef skipSponsorTextCompactHighlight = sf("sb_skip_button_compact_highlight");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefix to use when serializing to flat JSON layout used with ReVanced import/export.
|
||||||
|
*/
|
||||||
|
private static final String FLAT_JSON_IMPORT_EXPORT_PREFIX = "sb_";
|
||||||
|
|
||||||
private static final SegmentCategory[] categoriesWithoutHighlights = new SegmentCategory[]{
|
private static final SegmentCategory[] categoriesWithoutHighlights = new SegmentCategory[]{
|
||||||
SPONSOR,
|
SPONSOR,
|
||||||
SELF_PROMO,
|
SELF_PROMO,
|
||||||
@ -189,6 +197,8 @@ public enum SegmentCategory {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public CategoryBehaviour behaviour;
|
public CategoryBehaviour behaviour;
|
||||||
|
@NonNull
|
||||||
|
public final CategoryBehaviour defaultBehaviour;
|
||||||
|
|
||||||
SegmentCategory(String key, StringRef title, StringRef description,
|
SegmentCategory(String key, StringRef title, StringRef description,
|
||||||
StringRef skipButtonText,
|
StringRef skipButtonText,
|
||||||
@ -213,7 +223,7 @@ public enum SegmentCategory {
|
|||||||
this.skippedToastBeginning = Objects.requireNonNull(skippedToastBeginning);
|
this.skippedToastBeginning = Objects.requireNonNull(skippedToastBeginning);
|
||||||
this.skippedToastMiddle = Objects.requireNonNull(skippedToastMiddle);
|
this.skippedToastMiddle = Objects.requireNonNull(skippedToastMiddle);
|
||||||
this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd);
|
this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd);
|
||||||
this.behaviour = Objects.requireNonNull(defaultBehavior);
|
this.behaviour = this.defaultBehaviour = Objects.requireNonNull(defaultBehavior);
|
||||||
this.color = this.defaultColor = defaultColor;
|
this.color = this.defaultColor = defaultColor;
|
||||||
this.paint = new Paint();
|
this.paint = new Paint();
|
||||||
setColor(defaultColor);
|
setColor(defaultColor);
|
||||||
@ -231,10 +241,13 @@ public enum SegmentCategory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String behaviorString = preferences.getString(key, null);
|
String behaviorString = preferences.getString(key, null);
|
||||||
if (behaviorString != null) {
|
if (behaviorString == null) {
|
||||||
|
behaviour = defaultBehaviour;
|
||||||
|
} else {
|
||||||
CategoryBehaviour preferenceBehavior = CategoryBehaviour.byStringKey(behaviorString);
|
CategoryBehaviour preferenceBehavior = CategoryBehaviour.byStringKey(behaviorString);
|
||||||
if (preferenceBehavior == null) {
|
if (preferenceBehavior == null) {
|
||||||
LogHelper.printException(() -> "Unknown behavior: " + behaviorString); // should never happen
|
LogHelper.printException(() -> "Unknown behavior: " + behaviorString); // should never happen
|
||||||
|
behaviour = defaultBehaviour;
|
||||||
} else {
|
} else {
|
||||||
behaviour = preferenceBehavior;
|
behaviour = preferenceBehavior;
|
||||||
}
|
}
|
||||||
@ -253,6 +266,50 @@ public enum SegmentCategory {
|
|||||||
editor.putString(key, behaviour.key);
|
editor.putString(key, behaviour.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getFlatJsonBehaviorKey() {
|
||||||
|
return FLAT_JSON_IMPORT_EXPORT_PREFIX + key;
|
||||||
|
}
|
||||||
|
private String getFlatJsonColorKey() {
|
||||||
|
return FLAT_JSON_IMPORT_EXPORT_PREFIX + key + COLOR_PREFERENCE_KEY_SUFFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void exportToFlatJSON(JSONObject json) throws JSONException {
|
||||||
|
if (behaviour != defaultBehaviour) {
|
||||||
|
json.put(getFlatJsonBehaviorKey(), behaviour.key);
|
||||||
|
}
|
||||||
|
if (color != defaultColor) {
|
||||||
|
json.put(getFlatJsonColorKey(), colorString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calling code is responsible for calling {@link #updateEnabledCategories()} and {@link SharedPreferences.Editor#apply()}
|
||||||
|
*/
|
||||||
|
public int importFromFlatJSON(JSONObject json, SharedPreferences.Editor editor) throws JSONException {
|
||||||
|
int numberOfSettingsImported = 0;
|
||||||
|
String behaviorKey = getFlatJsonBehaviorKey();
|
||||||
|
if (json.has(behaviorKey)) {
|
||||||
|
String behaviorString = json.getString(behaviorKey);
|
||||||
|
CategoryBehaviour importedBehavior = CategoryBehaviour.byStringKey(behaviorString);
|
||||||
|
if (importedBehavior == null) {
|
||||||
|
throw new IllegalArgumentException("unknown behavior: " + behaviorString);
|
||||||
|
}
|
||||||
|
behaviour = importedBehavior;
|
||||||
|
numberOfSettingsImported++;
|
||||||
|
} else {
|
||||||
|
behaviour = defaultBehaviour;
|
||||||
|
}
|
||||||
|
String colorKey = getFlatJsonColorKey();
|
||||||
|
if (json.has(colorKey)) {
|
||||||
|
setColor(json.getString(colorKey));
|
||||||
|
numberOfSettingsImported++;
|
||||||
|
} else {
|
||||||
|
color = defaultColor;
|
||||||
|
}
|
||||||
|
save(editor);
|
||||||
|
return numberOfSettingsImported;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return HTML color format string
|
* @return HTML color format string
|
||||||
*/
|
*/
|
||||||
@ -300,7 +357,7 @@ public enum SegmentCategory {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
StringRef getSkipButtonText(long segmentStartTime, long videoLength) {
|
StringRef getSkipButtonText(long segmentStartTime, long videoLength) {
|
||||||
if (SettingsEnum.SB_USE_COMPACT_SKIP_BUTTON.getBoolean()) {
|
if (SettingsEnum.SB_COMPACT_SKIP_BUTTON.getBoolean()) {
|
||||||
return (this == SegmentCategory.HIGHLIGHT)
|
return (this == SegmentCategory.HIGHLIGHT)
|
||||||
? skipSponsorTextCompactHighlight
|
? skipSponsorTextCompactHighlight
|
||||||
: skipSponsorTextCompact;
|
: skipSponsorTextCompact;
|
||||||
|
@ -17,7 +17,12 @@ public class UserStats {
|
|||||||
* "User reputation". Unclear how SB determines this value.
|
* "User reputation". Unclear how SB determines this value.
|
||||||
*/
|
*/
|
||||||
public final float reputation;
|
public final float reputation;
|
||||||
|
/**
|
||||||
|
* {@link #segmentCount} plus {@link #ignoredSegmentCount}
|
||||||
|
*/
|
||||||
|
public final int totalSegmentCountIncludingIgnored;
|
||||||
public final int segmentCount;
|
public final int segmentCount;
|
||||||
|
public final int ignoredSegmentCount;
|
||||||
public final int viewCount;
|
public final int viewCount;
|
||||||
public final double minutesSaved;
|
public final double minutesSaved;
|
||||||
|
|
||||||
@ -26,6 +31,8 @@ public class UserStats {
|
|||||||
userName = json.getString("userName");
|
userName = json.getString("userName");
|
||||||
reputation = (float)json.getDouble("reputation");
|
reputation = (float)json.getDouble("reputation");
|
||||||
segmentCount = json.getInt("segmentCount");
|
segmentCount = json.getInt("segmentCount");
|
||||||
|
ignoredSegmentCount = json.getInt("ignoredSegmentCount");
|
||||||
|
totalSegmentCountIncludingIgnored = segmentCount + ignoredSegmentCount;
|
||||||
viewCount = json.getInt("viewCount");
|
viewCount = json.getInt("viewCount");
|
||||||
minutesSaved = json.getDouble("minutesSaved");
|
minutesSaved = json.getDouble("minutesSaved");
|
||||||
}
|
}
|
||||||
@ -38,6 +45,7 @@ public class UserStats {
|
|||||||
+ ", userName='" + userName + '\''
|
+ ", userName='" + userName + '\''
|
||||||
+ ", reputation=" + reputation
|
+ ", reputation=" + reputation
|
||||||
+ ", segmentCount=" + segmentCount
|
+ ", segmentCount=" + segmentCount
|
||||||
|
+ ", ignoredSegmentCount=" + ignoredSegmentCount
|
||||||
+ ", viewCount=" + viewCount
|
+ ", viewCount=" + viewCount
|
||||||
+ ", minutesSaved=" + minutesSaved
|
+ ", minutesSaved=" + minutesSaved
|
||||||
+ '}';
|
+ '}';
|
||||||
|
@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import app.revanced.integrations.requests.Requester;
|
import app.revanced.integrations.requests.Requester;
|
||||||
import app.revanced.integrations.requests.Route;
|
import app.revanced.integrations.requests.Route;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.sponsorblock.objects.CategoryBehaviour;
|
import app.revanced.integrations.sponsorblock.SponsorBlockSettings;
|
||||||
import app.revanced.integrations.sponsorblock.objects.SegmentCategory;
|
import app.revanced.integrations.sponsorblock.objects.SegmentCategory;
|
||||||
import app.revanced.integrations.sponsorblock.objects.SponsorSegment;
|
import app.revanced.integrations.sponsorblock.objects.SponsorSegment;
|
||||||
import app.revanced.integrations.sponsorblock.objects.SponsorSegment.SegmentVote;
|
import app.revanced.integrations.sponsorblock.objects.SponsorSegment.SegmentVote;
|
||||||
@ -49,6 +49,15 @@ public class SBRequester {
|
|||||||
private SBRequester() {
|
private SBRequester() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) {
|
||||||
|
if (SettingsEnum.SB_TOAST_ON_CONNECTION_ERROR.getBoolean()) {
|
||||||
|
ReVancedUtils.showToastShort(toastMessage);
|
||||||
|
}
|
||||||
|
if (ex != null) {
|
||||||
|
LogHelper.printInfo(() -> toastMessage, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static SponsorSegment[] getSegments(@NonNull String videoId) {
|
public static SponsorSegment[] getSegments(@NonNull String videoId) {
|
||||||
ReVancedUtils.verifyOffMainThread();
|
ReVancedUtils.verifyOffMainThread();
|
||||||
@ -59,7 +68,7 @@ public class SBRequester {
|
|||||||
|
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
JSONArray responseArray = Requester.parseJSONArray(connection);
|
JSONArray responseArray = Requester.parseJSONArray(connection);
|
||||||
final long minSegmentDuration = (long) (SettingsEnum.SB_MIN_DURATION.getFloat() * 1000);
|
final long minSegmentDuration = (long) (SettingsEnum.SB_SEGMENT_MIN_DURATION.getFloat() * 1000);
|
||||||
for (int i = 0, length = responseArray.length(); i < length; i++) {
|
for (int i = 0, length = responseArray.length(); i < length; i++) {
|
||||||
JSONObject obj = (JSONObject) responseArray.get(i);
|
JSONObject obj = (JSONObject) responseArray.get(i);
|
||||||
JSONArray segment = obj.getJSONArray("segment");
|
JSONArray segment = obj.getJSONArray("segment");
|
||||||
@ -88,14 +97,16 @@ public class SBRequester {
|
|||||||
// no segments are found. a normal response
|
// no segments are found. a normal response
|
||||||
LogHelper.printDebug(() -> "No segments found for video: " + videoId);
|
LogHelper.printDebug(() -> "No segments found for video: " + videoId);
|
||||||
} else {
|
} else {
|
||||||
LogHelper.printException(() -> "getSegments failed with response code: " + responseCode,
|
handleConnectionError(str("sb_sponsorblock_connection_failure_status", responseCode), null);
|
||||||
null, str("sb_sponsorblock_connection_failure_status", responseCode));
|
|
||||||
connection.disconnect(); // something went wrong, might as well disconnect
|
connection.disconnect(); // something went wrong, might as well disconnect
|
||||||
}
|
}
|
||||||
} catch (SocketTimeoutException ex) {
|
} catch (SocketTimeoutException ex) {
|
||||||
LogHelper.printException(() -> "Failed to get segments", ex, str("sb_sponsorblock_connection_failure_timeout"));
|
handleConnectionError(str("sb_sponsorblock_connection_failure_timeout"), ex);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
handleConnectionError(str("sb_sponsorblock_connection_failure_generic"), ex);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Failed to get segments", ex, str("sb_sponsorblock_connection_failure_generic"));
|
// Should never happen
|
||||||
|
LogHelper.printException(() -> "getSegments failure", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crude debug tests to verify random features
|
// Crude debug tests to verify random features
|
||||||
@ -127,15 +138,16 @@ public class SBRequester {
|
|||||||
return segments.toArray(new SponsorSegment[0]);
|
return segments.toArray(new SponsorSegment[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void submitSegments(@NonNull String userPrivateId, @NonNull String videoId, @NonNull String category,
|
public static void submitSegments(@NonNull String videoId, @NonNull String category,
|
||||||
long startTime, long endTime, long videoLength) {
|
long startTime, long endTime, long videoLength) {
|
||||||
ReVancedUtils.verifyOffMainThread();
|
ReVancedUtils.verifyOffMainThread();
|
||||||
try {
|
try {
|
||||||
|
String privateUserId = SponsorBlockSettings.getSBPrivateUserID();
|
||||||
String start = String.format(Locale.US, TIME_TEMPLATE, startTime / 1000f);
|
String start = String.format(Locale.US, TIME_TEMPLATE, startTime / 1000f);
|
||||||
String end = String.format(Locale.US, TIME_TEMPLATE, endTime / 1000f);
|
String end = String.format(Locale.US, TIME_TEMPLATE, endTime / 1000f);
|
||||||
String duration = String.format(Locale.US, TIME_TEMPLATE, videoLength / 1000f);
|
String duration = String.format(Locale.US, TIME_TEMPLATE, videoLength / 1000f);
|
||||||
|
|
||||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.SUBMIT_SEGMENTS, userPrivateId, videoId, category, start, end, duration);
|
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.SUBMIT_SEGMENTS, privateUserId, videoId, category, start, end, duration);
|
||||||
final int responseCode = connection.getResponseCode();
|
final int responseCode = connection.getResponseCode();
|
||||||
|
|
||||||
final String messageToToast;
|
final String messageToToast;
|
||||||
@ -161,7 +173,10 @@ public class SBRequester {
|
|||||||
}
|
}
|
||||||
ReVancedUtils.showToastLong(messageToToast);
|
ReVancedUtils.showToastLong(messageToToast);
|
||||||
} catch (SocketTimeoutException ex) {
|
} catch (SocketTimeoutException ex) {
|
||||||
|
// Always show, even if show connection toasts is turned off
|
||||||
ReVancedUtils.showToastLong(str("sb_submit_failed_timeout"));
|
ReVancedUtils.showToastLong(str("sb_submit_failed_timeout"));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ReVancedUtils.showToastLong(str("sb_submit_failed_unknown_error", 0, ex.getMessage()));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "failed to submit segments", ex);
|
LogHelper.printException(() -> "failed to submit segments", ex);
|
||||||
}
|
}
|
||||||
@ -196,7 +211,7 @@ public class SBRequester {
|
|||||||
ReVancedUtils.runOnBackgroundThread(() -> {
|
ReVancedUtils.runOnBackgroundThread(() -> {
|
||||||
try {
|
try {
|
||||||
String segmentUuid = segment.UUID;
|
String segmentUuid = segment.UUID;
|
||||||
String uuid = SettingsEnum.SB_UUID.getString();
|
String uuid = SponsorBlockSettings.getSBPrivateUserID();
|
||||||
HttpURLConnection connection = (voteOption == SegmentVote.CATEGORY_CHANGE)
|
HttpURLConnection connection = (voteOption == SegmentVote.CATEGORY_CHANGE)
|
||||||
? getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_CATEGORY, uuid, segmentUuid, categoryToVoteFor.key)
|
? getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_CATEGORY, uuid, segmentUuid, categoryToVoteFor.key)
|
||||||
: getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_QUALITY, uuid, segmentUuid, String.valueOf(voteOption.apiVoteType));
|
: getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_QUALITY, uuid, segmentUuid, String.valueOf(voteOption.apiVoteType));
|
||||||
@ -216,7 +231,9 @@ public class SBRequester {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (SocketTimeoutException ex) {
|
} catch (SocketTimeoutException ex) {
|
||||||
LogHelper.printException(() -> "failed to vote for segment", ex, str("sb_vote_failed_timeout"));
|
ReVancedUtils.showToastShort(str("sb_vote_failed_timeout"));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
ReVancedUtils.showToastShort(str("sb_vote_failed_unknown_error", 0, ex.getMessage()));
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "failed to vote for segment", ex); // should never happen
|
LogHelper.printException(() -> "failed to vote for segment", ex); // should never happen
|
||||||
}
|
}
|
||||||
@ -230,7 +247,7 @@ public class SBRequester {
|
|||||||
public static UserStats retrieveUserStats() {
|
public static UserStats retrieveUserStats() {
|
||||||
ReVancedUtils.verifyOffMainThread();
|
ReVancedUtils.verifyOffMainThread();
|
||||||
try {
|
try {
|
||||||
UserStats stats = new UserStats(getJSONObject(SBRoutes.GET_USER_STATS, SettingsEnum.SB_UUID.getString()));
|
UserStats stats = new UserStats(getJSONObject(SBRoutes.GET_USER_STATS, SponsorBlockSettings.getSBPrivateUserID()));
|
||||||
LogHelper.printDebug(() -> "user stats: " + stats);
|
LogHelper.printDebug(() -> "user stats: " + stats);
|
||||||
return stats;
|
return stats;
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
@ -248,7 +265,7 @@ public class SBRequester {
|
|||||||
public static String setUsername(@NonNull String username) {
|
public static String setUsername(@NonNull String username) {
|
||||||
ReVancedUtils.verifyOffMainThread();
|
ReVancedUtils.verifyOffMainThread();
|
||||||
try {
|
try {
|
||||||
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SettingsEnum.SB_UUID.getString(), username);
|
HttpURLConnection connection = getConnectionFromRoute(SBRoutes.CHANGE_USERNAME, SponsorBlockSettings.getSBPrivateUserID(), username);
|
||||||
final int responseCode = connection.getResponseCode();
|
final int responseCode = connection.getResponseCode();
|
||||||
String responseMessage = connection.getResponseMessage();
|
String responseMessage = connection.getResponseMessage();
|
||||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||||
@ -262,15 +279,18 @@ public class SBRequester {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void runVipCheckInBackgroundIfNeeded() {
|
public static void runVipCheckInBackgroundIfNeeded() {
|
||||||
|
if (!SponsorBlockSettings.userHasSBPrivateId()) {
|
||||||
|
return; // User cannot be a VIP. User has never voted, created any segments, or has imported a SB user id.
|
||||||
|
}
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
if (now < (SettingsEnum.SB_LAST_VIP_CHECK.getLong() + TimeUnit.DAYS.toMillis(3))) {
|
if (now < (SettingsEnum.SB_LAST_VIP_CHECK.getLong() + TimeUnit.DAYS.toMillis(3))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ReVancedUtils.runOnBackgroundThread(() -> {
|
ReVancedUtils.runOnBackgroundThread(() -> {
|
||||||
try {
|
try {
|
||||||
JSONObject json = getJSONObject(SBRoutes.IS_USER_VIP, SettingsEnum.SB_UUID.getString());
|
JSONObject json = getJSONObject(SBRoutes.IS_USER_VIP, SponsorBlockSettings.getSBPrivateUserID());
|
||||||
boolean vip = json.getBoolean("vip");
|
boolean vip = json.getBoolean("vip");
|
||||||
SettingsEnum.SB_IS_VIP.saveValue(vip);
|
SettingsEnum.SB_USER_IS_VIP.saveValue(vip);
|
||||||
SettingsEnum.SB_LAST_VIP_CHECK.saveValue(now);
|
SettingsEnum.SB_LAST_VIP_CHECK.saveValue(now);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
LogHelper.printInfo(() -> "Failed to check VIP (network error)", ex); // info, so no error toast is shown
|
LogHelper.printInfo(() -> "Failed to check VIP (network error)", ex); // info, so no error toast is shown
|
||||||
|
@ -9,7 +9,7 @@ class SBRoutes {
|
|||||||
static final Route IS_USER_VIP = new Route(GET, "/api/isUserVIP?userID={user_id}");
|
static final Route IS_USER_VIP = new Route(GET, "/api/isUserVIP?userID={user_id}");
|
||||||
static final Route GET_SEGMENTS = new Route(GET, "/api/skipSegments?videoID={video_id}&categories={categories}");
|
static final Route GET_SEGMENTS = new Route(GET, "/api/skipSegments?videoID={video_id}&categories={categories}");
|
||||||
static final Route VIEWED_SEGMENT = new Route(POST, "/api/viewedVideoSponsorTime?UUID={segment_id}");
|
static final Route VIEWED_SEGMENT = new Route(POST, "/api/viewedVideoSponsorTime?UUID={segment_id}");
|
||||||
static final Route GET_USER_STATS = new Route(GET, "/api/userInfo?userID={user_id}&values=[\"userID\",\"userName\",\"reputation\",\"segmentCount\",\"viewCount\",\"minutesSaved\"]");
|
static final Route GET_USER_STATS = new Route(GET, "/api/userInfo?userID={user_id}&values=[\"userID\",\"userName\",\"reputation\",\"segmentCount\",\"ignoredSegmentCount\",\"viewCount\",\"minutesSaved\"]");
|
||||||
static final Route CHANGE_USERNAME = new Route(POST, "/api/setUsername?userID={user_id}&username={username}");
|
static final Route CHANGE_USERNAME = new Route(POST, "/api/setUsername?userID={user_id}&username={username}");
|
||||||
static final Route SUBMIT_SEGMENTS = new Route(POST, "/api/skipSegments?userID={user_id}&videoID={video_id}&category={category}&startTime={start_time}&endTime={end_time}&videoDuration={duration}");
|
static final Route SUBMIT_SEGMENTS = new Route(POST, "/api/skipSegments?userID={user_id}&videoID={video_id}&category={category}&startTime={start_time}&endTime={end_time}&videoDuration={duration}");
|
||||||
static final Route VOTE_ON_SEGMENT_QUALITY = new Route(POST, "/api/voteOnSponsorTime?userID={user_id}&UUID={segment_id}&type={type}");
|
static final Route VOTE_ON_SEGMENT_QUALITY = new Route(POST, "/api/voteOnSponsorTime?userID={user_id}&UUID={segment_id}&type={type}");
|
||||||
|
@ -3,52 +3,35 @@ package app.revanced.integrations.sponsorblock.ui;
|
|||||||
import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier;
|
import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.integrations.patches.VideoInformation;
|
import app.revanced.integrations.patches.VideoInformation;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
import app.revanced.integrations.videoplayer.BottomControlButton;
|
||||||
|
|
||||||
public class CreateSegmentButtonController {
|
public class CreateSegmentButtonController {
|
||||||
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
|
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
|
||||||
private static Animation fadeIn;
|
|
||||||
private static Animation fadeOut;
|
|
||||||
private static boolean isShowing;
|
private static boolean isShowing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* injection point
|
* injection point
|
||||||
*/
|
*/
|
||||||
public static void initialize(Object viewStub) {
|
public static void initialize(View youtubeControlsLayout) {
|
||||||
try {
|
try {
|
||||||
LogHelper.printDebug(() -> "initializing new segment button");
|
LogHelper.printDebug(() -> "initializing new segment button");
|
||||||
|
ImageView imageView = Objects.requireNonNull(youtubeControlsLayout.findViewById(
|
||||||
RelativeLayout youtubeControlsLayout = (RelativeLayout) viewStub;
|
getResourceIdentifier("sb_sponsorblock_button", "id")));
|
||||||
String buttonIdentifier = "sb_sponsorblock_button";
|
imageView.setVisibility(View.GONE);
|
||||||
ImageView imageView = youtubeControlsLayout.findViewById(getResourceIdentifier(buttonIdentifier, "id"));
|
|
||||||
if (imageView == null) {
|
|
||||||
LogHelper.printException(() -> "Couldn't find imageView with \"" + buttonIdentifier + "\"");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
imageView.setOnClickListener(v -> {
|
imageView.setOnClickListener(v -> {
|
||||||
LogHelper.printDebug(() -> "New segment button clicked");
|
|
||||||
SponsorBlockViewController.toggleNewSegmentLayoutVisibility();
|
SponsorBlockViewController.toggleNewSegmentLayoutVisibility();
|
||||||
});
|
});
|
||||||
buttonReference = new WeakReference<>(imageView);
|
|
||||||
|
|
||||||
// Animations
|
buttonReference = new WeakReference<>(imageView);
|
||||||
if (fadeIn == null) {
|
|
||||||
fadeIn = ReVancedUtils.getResourceAnimation("fade_in");
|
|
||||||
fadeIn.setDuration(ReVancedUtils.getResourceInteger("fade_duration_fast"));
|
|
||||||
fadeOut = ReVancedUtils.getResourceAnimation("fade_out");
|
|
||||||
fadeOut.setDuration(ReVancedUtils.getResourceInteger("fade_duration_scheduled"));
|
|
||||||
}
|
|
||||||
isShowing = true;
|
|
||||||
changeVisibilityImmediate(false);
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "initialize failure", ex);
|
LogHelper.printException(() -> "initialize failure", ex);
|
||||||
}
|
}
|
||||||
@ -86,7 +69,7 @@ public class CreateSegmentButtonController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
iView.startAnimation(fadeIn);
|
iView.startAnimation(BottomControlButton.getButtonFadeIn());
|
||||||
}
|
}
|
||||||
iView.setVisibility(View.VISIBLE);
|
iView.setVisibility(View.VISIBLE);
|
||||||
return;
|
return;
|
||||||
@ -95,7 +78,7 @@ public class CreateSegmentButtonController {
|
|||||||
if (iView.getVisibility() == View.VISIBLE) {
|
if (iView.getVisibility() == View.VISIBLE) {
|
||||||
iView.clearAnimation();
|
iView.clearAnimation();
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
iView.startAnimation(fadeOut);
|
iView.startAnimation(BottomControlButton.getButtonFadeOut());
|
||||||
}
|
}
|
||||||
iView.setVisibility(View.GONE);
|
iView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@ -105,7 +88,7 @@ public class CreateSegmentButtonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldBeShown() {
|
private static boolean shouldBeShown() {
|
||||||
return SettingsEnum.SB_ENABLED.getBoolean() && SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.getBoolean()
|
return SettingsEnum.SB_ENABLED.getBoolean() && SettingsEnum.SB_CREATE_NEW_SEGMENT.getBoolean()
|
||||||
&& !VideoInformation.isAtEndOfVideo();
|
&& !VideoInformation.isAtEndOfVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,14 +53,14 @@ public final class NewSegmentLayout extends FrameLayout {
|
|||||||
initializeButton(
|
initializeButton(
|
||||||
context,
|
context,
|
||||||
"sb_new_segment_rewind",
|
"sb_new_segment_rewind",
|
||||||
() -> VideoInformation.seekToRelative(-SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getInt()),
|
() -> VideoInformation.seekToRelative(-SettingsEnum.SB_CREATE_NEW_SEGMENT_STEP.getInt()),
|
||||||
"Rewind button clicked"
|
"Rewind button clicked"
|
||||||
);
|
);
|
||||||
|
|
||||||
initializeButton(
|
initializeButton(
|
||||||
context,
|
context,
|
||||||
"sb_new_segment_forward",
|
"sb_new_segment_forward",
|
||||||
() -> VideoInformation.seekToRelative(SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.getInt()),
|
() -> VideoInformation.seekToRelative(SettingsEnum.SB_CREATE_NEW_SEGMENT_STEP.getInt()),
|
||||||
"Forward button clicked"
|
"Forward button clicked"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ public class SponsorBlockViewController {
|
|||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void initialize(Object obj) {
|
public static void initialize(ViewGroup viewGroup) {
|
||||||
try {
|
try {
|
||||||
LogHelper.printDebug(() -> "initializing");
|
LogHelper.printDebug(() -> "initializing");
|
||||||
|
|
||||||
@ -64,7 +64,6 @@ public class SponsorBlockViewController {
|
|||||||
LayoutInflater.from(context).inflate(getResourceIdentifier("inline_sponsor_overlay", "layout"), layout);
|
LayoutInflater.from(context).inflate(getResourceIdentifier("inline_sponsor_overlay", "layout"), layout);
|
||||||
inlineSponsorOverlayRef = new WeakReference<>(layout);
|
inlineSponsorOverlayRef = new WeakReference<>(layout);
|
||||||
|
|
||||||
ViewGroup viewGroup = (ViewGroup) obj;
|
|
||||||
viewGroup.addView(layout);
|
viewGroup.addView(layout);
|
||||||
viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
|
viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
@ -214,7 +213,7 @@ public class SponsorBlockViewController {
|
|||||||
// the buttons automatically set themselves to visible when appropriate,
|
// the buttons automatically set themselves to visible when appropriate,
|
||||||
// but if buttons are showing when the end of the video is reached then they need
|
// but if buttons are showing when the end of the video is reached then they need
|
||||||
// to be forcefully hidden
|
// to be forcefully hidden
|
||||||
if (!SettingsEnum.PREFERRED_AUTO_REPEAT.getBoolean()) {
|
if (!SettingsEnum.AUTO_REPEAT.getBoolean()) {
|
||||||
CreateSegmentButtonController.hide();
|
CreateSegmentButtonController.hide();
|
||||||
VotingButtonController.hide();
|
VotingButtonController.hide();
|
||||||
}
|
}
|
||||||
|
@ -3,11 +3,10 @@ package app.revanced.integrations.sponsorblock.ui;
|
|||||||
import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier;
|
import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier;
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.RelativeLayout;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.integrations.patches.VideoInformation;
|
import app.revanced.integrations.patches.VideoInformation;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
@ -15,40 +14,26 @@ import app.revanced.integrations.sponsorblock.SegmentPlaybackController;
|
|||||||
import app.revanced.integrations.sponsorblock.SponsorBlockUtils;
|
import app.revanced.integrations.sponsorblock.SponsorBlockUtils;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
import app.revanced.integrations.videoplayer.BottomControlButton;
|
||||||
|
|
||||||
public class VotingButtonController {
|
public class VotingButtonController {
|
||||||
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
|
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
|
||||||
private static Animation fadeIn;
|
|
||||||
private static Animation fadeOut;
|
|
||||||
private static boolean isShowing;
|
private static boolean isShowing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* injection point
|
* injection point
|
||||||
*/
|
*/
|
||||||
public static void initialize(Object viewStub) {
|
public static void initialize(View youtubeControlsLayout) {
|
||||||
try {
|
try {
|
||||||
LogHelper.printDebug(() -> "initializing voting button");
|
LogHelper.printDebug(() -> "initializing voting button");
|
||||||
RelativeLayout controlsLayout = (RelativeLayout) viewStub;
|
ImageView imageView = Objects.requireNonNull(youtubeControlsLayout.findViewById(
|
||||||
String buttonResourceName = "sb_voting_button";
|
getResourceIdentifier("sb_voting_button", "id")));
|
||||||
ImageView imageView = controlsLayout.findViewById(getResourceIdentifier(buttonResourceName, "id"));
|
imageView.setVisibility(View.GONE);
|
||||||
if (imageView == null) {
|
|
||||||
LogHelper.printException(() -> "Couldn't find imageView with \"" + buttonResourceName + "\"");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
imageView.setOnClickListener(v -> {
|
imageView.setOnClickListener(v -> {
|
||||||
SponsorBlockUtils.onVotingClicked(v.getContext());
|
SponsorBlockUtils.onVotingClicked(v.getContext());
|
||||||
});
|
});
|
||||||
buttonReference = new WeakReference<>(imageView);
|
|
||||||
|
|
||||||
// Animations
|
buttonReference = new WeakReference<>(imageView);
|
||||||
if (fadeIn == null) {
|
|
||||||
fadeIn = ReVancedUtils.getResourceAnimation("fade_in");
|
|
||||||
fadeIn.setDuration(ReVancedUtils.getResourceInteger("fade_duration_fast"));
|
|
||||||
fadeOut = ReVancedUtils.getResourceAnimation("fade_out");
|
|
||||||
fadeOut.setDuration(ReVancedUtils.getResourceInteger("fade_duration_scheduled"));
|
|
||||||
}
|
|
||||||
isShowing = true;
|
|
||||||
changeVisibilityImmediate(false);
|
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "Unable to set RelativeLayout", ex);
|
LogHelper.printException(() -> "Unable to set RelativeLayout", ex);
|
||||||
}
|
}
|
||||||
@ -86,7 +71,7 @@ public class VotingButtonController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
iView.startAnimation(fadeIn);
|
iView.startAnimation(BottomControlButton.getButtonFadeIn());
|
||||||
}
|
}
|
||||||
iView.setVisibility(View.VISIBLE);
|
iView.setVisibility(View.VISIBLE);
|
||||||
return;
|
return;
|
||||||
@ -95,7 +80,7 @@ public class VotingButtonController {
|
|||||||
if (iView.getVisibility() == View.VISIBLE) {
|
if (iView.getVisibility() == View.VISIBLE) {
|
||||||
iView.clearAnimation();
|
iView.clearAnimation();
|
||||||
if (!immediate) {
|
if (!immediate) {
|
||||||
iView.startAnimation(fadeOut);
|
iView.startAnimation(BottomControlButton.getButtonFadeOut());
|
||||||
}
|
}
|
||||||
iView.setVisibility(View.GONE);
|
iView.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@ -105,7 +90,7 @@ public class VotingButtonController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldBeShown() {
|
private static boolean shouldBeShown() {
|
||||||
return SettingsEnum.SB_ENABLED.getBoolean() && SettingsEnum.SB_VOTING_ENABLED.getBoolean()
|
return SettingsEnum.SB_ENABLED.getBoolean() && SettingsEnum.SB_VOTING_BUTTON.getBoolean()
|
||||||
&& SegmentPlaybackController.videoHasSegments() && !VideoInformation.isAtEndOfVideo();
|
&& SegmentPlaybackController.videoHasSegments() && !VideoInformation.isAtEndOfVideo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +101,6 @@ public class VotingButtonController {
|
|||||||
ReVancedUtils.verifyOnMainThread();
|
ReVancedUtils.verifyOnMainThread();
|
||||||
View v = buttonReference.get();
|
View v = buttonReference.get();
|
||||||
if (v == null) {
|
if (v == null) {
|
||||||
LogHelper.printDebug(() -> "Cannot hide voting button (value is null)");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
v.setVisibility(View.GONE);
|
v.setVisibility(View.GONE);
|
||||||
|
@ -24,13 +24,13 @@ class SwipeControlsConfigurationProvider(
|
|||||||
* should swipe controls for volume be enabled?
|
* should swipe controls for volume be enabled?
|
||||||
*/
|
*/
|
||||||
val enableVolumeControls: Boolean
|
val enableVolumeControls: Boolean
|
||||||
get() = SettingsEnum.ENABLE_SWIPE_VOLUME.boolean
|
get() = SettingsEnum.SWIPE_VOLUME.boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* should swipe controls for volume be enabled?
|
* should swipe controls for volume be enabled?
|
||||||
*/
|
*/
|
||||||
val enableBrightnessControl: Boolean
|
val enableBrightnessControl: Boolean
|
||||||
get() = SettingsEnum.ENABLE_SWIPE_BRIGHTNESS.boolean
|
get() = SettingsEnum.SWIPE_BRIGHTNESS.boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* is the video player currently in fullscreen mode?
|
* is the video player currently in fullscreen mode?
|
||||||
@ -52,14 +52,14 @@ class SwipeControlsConfigurationProvider(
|
|||||||
* should press-to-swipe be enabled?
|
* should press-to-swipe be enabled?
|
||||||
*/
|
*/
|
||||||
val shouldEnablePressToSwipe: Boolean
|
val shouldEnablePressToSwipe: Boolean
|
||||||
get() = SettingsEnum.ENABLE_PRESS_TO_SWIPE.boolean
|
get() = SettingsEnum.SWIPE_PRESS_TO_ENGAGE.boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* threshold for swipe detection
|
* threshold for swipe detection
|
||||||
* this may be called rapidly in onScroll, so we have to load it once and then leave it constant
|
* this may be called rapidly in onScroll, so we have to load it once and then leave it constant
|
||||||
*/
|
*/
|
||||||
val swipeMagnitudeThreshold: Float
|
val swipeMagnitudeThreshold: Int
|
||||||
get() = SettingsEnum.SWIPE_MAGNITUDE_THRESHOLD.float
|
get() = SettingsEnum.SWIPE_MAGNITUDE_THRESHOLD.int
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
//region overlay adjustments
|
//region overlay adjustments
|
||||||
@ -68,7 +68,7 @@ class SwipeControlsConfigurationProvider(
|
|||||||
* should the overlay enable haptic feedback?
|
* should the overlay enable haptic feedback?
|
||||||
*/
|
*/
|
||||||
val shouldEnableHapticFeedback: Boolean
|
val shouldEnableHapticFeedback: Boolean
|
||||||
get() = SettingsEnum.ENABLE_SWIPE_HAPTIC_FEEDBACK.boolean
|
get() = SettingsEnum.SWIPE_HAPTIC_FEEDBACK.boolean
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* how long the overlay should be shown on changes
|
* how long the overlay should be shown on changes
|
||||||
@ -79,8 +79,8 @@ class SwipeControlsConfigurationProvider(
|
|||||||
/**
|
/**
|
||||||
* text size for the overlay, in sp
|
* text size for the overlay, in sp
|
||||||
*/
|
*/
|
||||||
val overlayTextSize: Float
|
val overlayTextSize: Int
|
||||||
get() = SettingsEnum.SWIPE_OVERLAY_TEXT_SIZE.float
|
get() = SettingsEnum.SWIPE_OVERLAY_TEXT_SIZE.int
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get the background color for text on the overlay, as a color int
|
* get the background color for text on the overlay, as a color int
|
||||||
|
@ -74,7 +74,7 @@ class SwipeControlsOverlayLayout(
|
|||||||
setColor(config.overlayTextBackgroundColor)
|
setColor(config.overlayTextBackgroundColor)
|
||||||
}
|
}
|
||||||
setTextColor(config.overlayForegroundColor)
|
setTextColor(config.overlayForegroundColor)
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, config.overlayTextSize)
|
setTextSize(TypedValue.COMPLEX_UNIT_SP, config.overlayTextSize.toFloat())
|
||||||
compoundDrawablePadding = compoundIconPadding
|
compoundDrawablePadding = compoundIconPadding
|
||||||
visibility = GONE
|
visibility = GONE
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ public class LogHelper {
|
|||||||
} else {
|
} else {
|
||||||
Log.e(logMessage, messageString, ex);
|
Log.e(logMessage, messageString, ex);
|
||||||
}
|
}
|
||||||
if (SettingsEnum.DEBUG_SHOW_TOAST_ON_ERROR.getBoolean()) {
|
if (SettingsEnum.DEBUG_TOAST_ON_ERROR.getBoolean()) {
|
||||||
String toastMessageToDisplay = (userToastMessage != null)
|
String toastMessageToDisplay = (userToastMessage != null)
|
||||||
? userToastMessage
|
? userToastMessage
|
||||||
: outerClassSimpleName + ": " + messageString;
|
: outerClassSimpleName + ": " + messageString;
|
||||||
|
@ -6,9 +6,15 @@ import android.content.res.Resources;
|
|||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -22,6 +28,8 @@ import java.util.concurrent.SynchronousQueue;
|
|||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
|
||||||
public class ReVancedUtils {
|
public class ReVancedUtils {
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
@ -30,6 +38,35 @@ public class ReVancedUtils {
|
|||||||
private ReVancedUtils() {
|
private ReVancedUtils() {
|
||||||
} // utility class
|
} // utility class
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide a view by setting its layout height and width to 1dp.
|
||||||
|
*
|
||||||
|
* @param condition The setting to check for hiding the view.
|
||||||
|
* @param view The view to hide.
|
||||||
|
*/
|
||||||
|
public static void hideViewBy1dpUnderCondition(SettingsEnum condition, View view) {
|
||||||
|
if (!condition.getBoolean()) return;
|
||||||
|
|
||||||
|
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
||||||
|
|
||||||
|
hideViewByLayoutParams(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide a view by setting its visibility to GONE.
|
||||||
|
*
|
||||||
|
* @param condition The setting to check for hiding the view.
|
||||||
|
* @param view The view to hide.
|
||||||
|
*/
|
||||||
|
public static void hideViewUnderCondition(SettingsEnum condition, View view) {
|
||||||
|
if (!condition.getBoolean()) return;
|
||||||
|
|
||||||
|
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
||||||
|
|
||||||
|
view.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* General purpose pool for network calls and other background tasks.
|
* General purpose pool for network calls and other background tasks.
|
||||||
* All tasks run at max thread priority.
|
* All tasks run at max thread priority.
|
||||||
@ -97,6 +134,24 @@ public class ReVancedUtils {
|
|||||||
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
return getContext().getResources().getDimension(getResourceIdentifier(resourceIdentifierName, "dimen"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The first child view that matches the filter.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static <T extends View> T getChildView(@NonNull ViewGroup viewGroup, @NonNull MatchFilter filter) {
|
||||||
|
for (int i = 0, childCount = viewGroup.getChildCount(); i < childCount; i++) {
|
||||||
|
View childAt = viewGroup.getChildAt(i);
|
||||||
|
if (filter.matches(childAt)) {
|
||||||
|
return (T) childAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface MatchFilter<T> {
|
||||||
|
boolean matches(T object);
|
||||||
|
}
|
||||||
|
|
||||||
public static Context getContext() {
|
public static Context getContext() {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
return context;
|
return context;
|
||||||
@ -117,6 +172,7 @@ public class ReVancedUtils {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Boolean isRightToLeftTextLayout;
|
private static Boolean isRightToLeftTextLayout;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the device language uses right to left text layout (hebrew, arabic, etc)
|
* If the device language uses right to left text layout (hebrew, arabic, etc)
|
||||||
*/
|
*/
|
||||||
@ -239,6 +295,31 @@ public class ReVancedUtils {
|
|||||||
|| (type == ConnectivityManager.TYPE_BLUETOOTH) ? NetworkType.MOBILE : NetworkType.OTHER;
|
|| (type == ConnectivityManager.TYPE_BLUETOOTH) ? NetworkType.MOBILE : NetworkType.OTHER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide a view by setting its layout params to 1x1
|
||||||
|
* @param view The view to hide.
|
||||||
|
*/
|
||||||
|
public static void hideViewByLayoutParams(View view) {
|
||||||
|
if (view instanceof LinearLayout) {
|
||||||
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(1, 1);
|
||||||
|
view.setLayoutParams(layoutParams);
|
||||||
|
} else if (view instanceof FrameLayout) {
|
||||||
|
FrameLayout.LayoutParams layoutParams2 = new FrameLayout.LayoutParams(1, 1);
|
||||||
|
view.setLayoutParams(layoutParams2);
|
||||||
|
} else if (view instanceof RelativeLayout) {
|
||||||
|
RelativeLayout.LayoutParams layoutParams3 = new RelativeLayout.LayoutParams(1, 1);
|
||||||
|
view.setLayoutParams(layoutParams3);
|
||||||
|
} else if (view instanceof Toolbar) {
|
||||||
|
Toolbar.LayoutParams layoutParams4 = new Toolbar.LayoutParams(1, 1);
|
||||||
|
view.setLayoutParams(layoutParams4);
|
||||||
|
} else if (view instanceof ViewGroup) {
|
||||||
|
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(1, 1);
|
||||||
|
view.setLayoutParams(layoutParams5);
|
||||||
|
} else {
|
||||||
|
LogHelper.printDebug(() -> "Hidden view with id " + view.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum NetworkType {
|
public enum NetworkType {
|
||||||
NONE,
|
NONE,
|
||||||
MOBILE,
|
MOBILE,
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
package app.revanced.integrations.utils;
|
package app.revanced.integrations.utils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
|
||||||
public class ThemeHelper {
|
public class ThemeHelper {
|
||||||
private static int themeValue;
|
private static int themeValue;
|
||||||
|
|
||||||
public static void setTheme(int value) {
|
|
||||||
if (themeValue != value) {
|
|
||||||
themeValue = value;
|
|
||||||
LogHelper.printDebug(() -> "Theme value: " + themeValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void setTheme(Object value) {
|
public static void setTheme(Object value) {
|
||||||
final int newOrdinalValue = ((Enum) value).ordinal();
|
final int newOrdinalValue = ((Enum) value).ordinal();
|
||||||
if (themeValue != newOrdinalValue) {
|
if (themeValue != newOrdinalValue) {
|
||||||
themeValue = newOrdinalValue;
|
themeValue = newOrdinalValue;
|
||||||
LogHelper.printDebug(() -> "Theme value: " + themeValue);
|
LogHelper.printDebug(() -> "Theme value: " + newOrdinalValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,4 +17,11 @@ public class ThemeHelper {
|
|||||||
return themeValue == 1;
|
return themeValue == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void setActivityTheme(Activity activity) {
|
||||||
|
final var theme = isDarkTheme()
|
||||||
|
? "Theme.YouTube.Settings.Dark"
|
||||||
|
: "Theme.YouTube.Settings";
|
||||||
|
activity.setTheme(ReVancedUtils.getResourceIdentifier(theme, "style"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,35 +4,52 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import androidx.annotation.Nullable;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import app.revanced.integrations.settings.SettingsEnum;
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
import app.revanced.integrations.utils.ReVancedUtils;
|
||||||
|
|
||||||
public abstract class BottomControlButton {
|
public abstract class BottomControlButton {
|
||||||
private static final Animation fadeIn = ReVancedUtils.getResourceAnimation("fade_in");
|
private static final Animation fadeIn;
|
||||||
private static final Animation fadeOut = ReVancedUtils.getResourceAnimation("fade_out");
|
private static final Animation fadeOut;
|
||||||
|
|
||||||
private final WeakReference<ImageView> buttonRef;
|
private final WeakReference<ImageView> buttonRef;
|
||||||
private final SettingsEnum setting;
|
private final SettingsEnum setting;
|
||||||
protected boolean isVisible;
|
protected boolean isVisible;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// TODO: check if these durations are correct.
|
// TODO: check if these durations are correct.
|
||||||
|
fadeIn = ReVancedUtils.getResourceAnimation("fade_in");
|
||||||
fadeIn.setDuration(ReVancedUtils.getResourceInteger("fade_duration_fast"));
|
fadeIn.setDuration(ReVancedUtils.getResourceInteger("fade_duration_fast"));
|
||||||
|
|
||||||
|
fadeOut = ReVancedUtils.getResourceAnimation("fade_out");
|
||||||
fadeOut.setDuration(ReVancedUtils.getResourceInteger("fade_duration_scheduled"));
|
fadeOut.setDuration(ReVancedUtils.getResourceInteger("fade_duration_scheduled"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Animation getButtonFadeIn() {
|
||||||
|
return fadeIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static Animation getButtonFadeOut() {
|
||||||
|
return fadeOut;
|
||||||
|
}
|
||||||
|
|
||||||
public BottomControlButton(@NonNull ViewGroup bottomControlsViewGroup, @NonNull String imageViewButtonId,
|
public BottomControlButton(@NonNull ViewGroup bottomControlsViewGroup, @NonNull String imageViewButtonId,
|
||||||
@NonNull SettingsEnum booleanSetting, @NonNull View.OnClickListener onClickListener) {
|
@NonNull SettingsEnum booleanSetting, @NonNull View.OnClickListener onClickListener,
|
||||||
|
@Nullable View.OnLongClickListener longClickListener) {
|
||||||
LogHelper.printDebug(() -> "Initializing button: " + imageViewButtonId);
|
LogHelper.printDebug(() -> "Initializing button: " + imageViewButtonId);
|
||||||
|
|
||||||
if (booleanSetting.returnType != SettingsEnum.ReturnType.BOOLEAN) {
|
if (booleanSetting.returnType != SettingsEnum.ReturnType.BOOLEAN) {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
setting = booleanSetting;
|
setting = booleanSetting;
|
||||||
|
|
||||||
// Create the button.
|
// Create the button.
|
||||||
@ -40,6 +57,9 @@ public abstract class BottomControlButton {
|
|||||||
ReVancedUtils.getResourceIdentifier(imageViewButtonId, "id")
|
ReVancedUtils.getResourceIdentifier(imageViewButtonId, "id")
|
||||||
));
|
));
|
||||||
imageView.setOnClickListener(onClickListener);
|
imageView.setOnClickListener(onClickListener);
|
||||||
|
if (longClickListener != null) {
|
||||||
|
imageView.setOnLongClickListener(longClickListener);
|
||||||
|
}
|
||||||
imageView.setVisibility(View.GONE);
|
imageView.setVisibility(View.GONE);
|
||||||
|
|
||||||
buttonRef = new WeakReference<>(imageView);
|
buttonRef = new WeakReference<>(imageView);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package app.revanced.integrations.videoplayer;
|
package app.revanced.integrations.videoplayer;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -16,17 +17,21 @@ public class CopyVideoUrlButton extends BottomControlButton {
|
|||||||
super(
|
super(
|
||||||
viewGroup,
|
viewGroup,
|
||||||
"copy_video_url_button",
|
"copy_video_url_button",
|
||||||
SettingsEnum.COPY_VIDEO_URL_BUTTON_SHOWN,
|
SettingsEnum.COPY_VIDEO_URL,
|
||||||
view -> CopyVideoUrlPatch.copyUrl(false)
|
view -> CopyVideoUrlPatch.copyUrl(false),
|
||||||
|
view -> {
|
||||||
|
CopyVideoUrlPatch.copyUrl(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void initializeButton(Object obj) {
|
public static void initializeButton(View view) {
|
||||||
try {
|
try {
|
||||||
instance = new CopyVideoUrlButton((ViewGroup) obj);
|
instance = new CopyVideoUrlButton((ViewGroup) view);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "initializeButton failure", ex);
|
LogHelper.printException(() -> "initializeButton failure", ex);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package app.revanced.integrations.videoplayer;
|
package app.revanced.integrations.videoplayer;
|
||||||
|
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -16,15 +17,19 @@ public class CopyVideoUrlTimestampButton extends BottomControlButton {
|
|||||||
super(
|
super(
|
||||||
bottomControlsViewGroup,
|
bottomControlsViewGroup,
|
||||||
"copy_video_url_timestamp_button",
|
"copy_video_url_timestamp_button",
|
||||||
SettingsEnum.COPY_VIDEO_URL_TIMESTAMP_BUTTON_SHOWN,
|
SettingsEnum.COPY_VIDEO_URL_TIMESTAMP,
|
||||||
view -> CopyVideoUrlPatch.copyUrl(true)
|
view -> CopyVideoUrlPatch.copyUrl(true),
|
||||||
|
view -> {
|
||||||
|
CopyVideoUrlPatch.copyUrl(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void initializeButton(Object bottomControlsViewGroup) {
|
public static void initializeButton(View bottomControlsViewGroup) {
|
||||||
try {
|
try {
|
||||||
instance = new CopyVideoUrlTimestampButton((ViewGroup) bottomControlsViewGroup);
|
instance = new CopyVideoUrlTimestampButton((ViewGroup) bottomControlsViewGroup);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -21,17 +21,18 @@ public class DownloadButton extends BottomControlButton {
|
|||||||
super(
|
super(
|
||||||
viewGroup,
|
viewGroup,
|
||||||
"download_button",
|
"download_button",
|
||||||
SettingsEnum.DOWNLOADS_BUTTON_SHOWN,
|
SettingsEnum.EXTERNAL_DOWNLOADER,
|
||||||
DownloadButton::onDownloadClick
|
DownloadButton::onDownloadClick,
|
||||||
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Injection point.
|
* Injection point.
|
||||||
*/
|
*/
|
||||||
public static void initializeButton(Object obj) {
|
public static void initializeButton(View view) {
|
||||||
try {
|
try {
|
||||||
instance = new DownloadButton((ViewGroup) obj);
|
instance = new DownloadButton((ViewGroup) view);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "initializeButton failure", ex);
|
LogHelper.printException(() -> "initializeButton failure", ex);
|
||||||
}
|
}
|
||||||
@ -48,7 +49,7 @@ public class DownloadButton extends BottomControlButton {
|
|||||||
LogHelper.printDebug(() -> "Download button clicked");
|
LogHelper.printDebug(() -> "Download button clicked");
|
||||||
|
|
||||||
final var context = view.getContext();
|
final var context = view.getContext();
|
||||||
var downloaderPackageName = SettingsEnum.DOWNLOADS_PACKAGE_NAME.getString();
|
var downloaderPackageName = SettingsEnum.EXTERNAL_DOWNLOADER_PACKAGE_NAME.getString();
|
||||||
|
|
||||||
boolean packageEnabled = false;
|
boolean packageEnabled = false;
|
||||||
try {
|
try {
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package app.revanced.reddit.patches;
|
||||||
|
|
||||||
|
import app.revanced.integrations.utils.LogHelper;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
public final class SanitizeUrlQueryPatch {
|
||||||
|
/**
|
||||||
|
* Strip query parameters from a given URL string.
|
||||||
|
*
|
||||||
|
* @param urlString URL string to strip query parameters from.
|
||||||
|
* @return URL string without query parameters if possible, otherwise the original string.
|
||||||
|
*/
|
||||||
|
public static String stripQueryParameters(final String urlString) {
|
||||||
|
try {
|
||||||
|
final var url = new URL(urlString);
|
||||||
|
|
||||||
|
return url.getProtocol() + "://" + url.getHost() + url.getPath();
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
LogHelper.printException(() -> "Can not parse URL", e);
|
||||||
|
return urlString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.twitch.patches;
|
||||||
|
|
||||||
|
import app.revanced.twitch.settings.SettingsEnum;
|
||||||
|
|
||||||
|
public class AutoClaimChannelPointsPatch {
|
||||||
|
public static boolean shouldAutoClaim() {
|
||||||
|
return SettingsEnum.AUTO_CLAIM_CHANNEL_POINTS.getBoolean();
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ public enum SettingsEnum {
|
|||||||
|
|
||||||
/* Chat */
|
/* Chat */
|
||||||
SHOW_DELETED_MESSAGES("revanced_show_deleted_messages", STRING, "cross-out"),
|
SHOW_DELETED_MESSAGES("revanced_show_deleted_messages", STRING, "cross-out"),
|
||||||
|
AUTO_CLAIM_CHANNEL_POINTS("revanced_auto_claim_channel_points", BOOLEAN, TRUE),
|
||||||
|
|
||||||
/* Misc */
|
/* Misc */
|
||||||
DEBUG_MODE("revanced_debug_mode", BOOLEAN, FALSE, true);
|
DEBUG_MODE("revanced_debug_mode", BOOLEAN, FALSE, true);
|
||||||
@ -153,6 +154,14 @@ public enum SettingsEnum {
|
|||||||
return (String) value;
|
return (String) value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value of this setting as as generic object type.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Object getObjectValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public enum ReturnType {
|
public enum ReturnType {
|
||||||
BOOLEAN,
|
BOOLEAN,
|
||||||
INTEGER,
|
INTEGER,
|
||||||
|
@ -88,7 +88,23 @@ public class ReVancedSettingsFragment extends PreferenceFragment {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sync all preferences with UI
|
// TODO: for a developer that uses Twitch: remove duplicated settings data
|
||||||
|
// 1. remove all default values from the Patches Setting preferences (SwitchPreference, TextPreference, ListPreference)
|
||||||
|
// 2. enable this code and verify the default is applied
|
||||||
|
if (false) {
|
||||||
|
for (SettingsEnum setting : SettingsEnum.values()) {
|
||||||
|
Preference pref = this.findPreference(setting.path);
|
||||||
|
if (pref instanceof SwitchPreference) {
|
||||||
|
((SwitchPreference) pref).setChecked(setting.getBoolean());
|
||||||
|
} else if (pref instanceof EditTextPreference) {
|
||||||
|
((EditTextPreference) pref).setText(setting.getObjectValue().toString());
|
||||||
|
} else if (pref instanceof ListPreference) {
|
||||||
|
((ListPreference) pref).setValue(setting.getObjectValue().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: remove this line. On load the UI should apply the values from Settings using the code above.
|
||||||
|
// It should not apply the UI values to the Settings here
|
||||||
syncPreference(null);
|
syncPreference(null);
|
||||||
|
|
||||||
this.registered = true;
|
this.registered = true;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package app.revanced.twitter.patches.hook.json
|
package app.revanced.twitter.patches.hook.json
|
||||||
|
|
||||||
|
import app.revanced.twitter.patches.hook.patch.dummy.DummyHook
|
||||||
import app.revanced.twitter.utils.json.JsonUtils.parseJson
|
import app.revanced.twitter.utils.json.JsonUtils.parseJson
|
||||||
import app.revanced.twitter.utils.stream.StreamUtils
|
import app.revanced.twitter.utils.stream.StreamUtils
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
@ -7,8 +8,9 @@ import java.io.IOException
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
object JsonHookPatch {
|
object JsonHookPatch {
|
||||||
|
// Additional hooks added by corresponding patch.
|
||||||
private val hooks = buildList<JsonHook> {
|
private val hooks = buildList<JsonHook> {
|
||||||
// Modified by corresponding patch.
|
add(DummyHook)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package app.revanced.twitter.patches.hook.patch.dummy
|
||||||
|
|
||||||
|
import app.revanced.twitter.patches.hook.json.BaseJsonHook
|
||||||
|
import app.revanced.twitter.patches.hook.json.JsonHookPatch
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dummy hook to reserve a register in [JsonHookPatch.hooks] list.
|
||||||
|
*/
|
||||||
|
object DummyHook : BaseJsonHook() {
|
||||||
|
override fun apply(json: JSONObject) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
@ -51,7 +51,7 @@ internal object TwiFucker {
|
|||||||
|
|
||||||
private fun JSONObject.dataCheckAndRemove() {
|
private fun JSONObject.dataCheckAndRemove() {
|
||||||
dataGetInstructions()?.forEach { instruction ->
|
dataGetInstructions()?.forEach { instruction ->
|
||||||
instruction.instructionCheckAndRemove()
|
instruction.instructionCheckAndRemove { it.entriesRemoveAnnoyance() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,9 +107,9 @@ internal object TwiFucker {
|
|||||||
private fun JSONObject.instructionGetAddEntries(): JSONArray? =
|
private fun JSONObject.instructionGetAddEntries(): JSONArray? =
|
||||||
optJSONObject("addEntries")?.optJSONArray("entries")
|
optJSONObject("addEntries")?.optJSONArray("entries")
|
||||||
|
|
||||||
private fun JSONObject.instructionCheckAndRemove() {
|
private fun JSONObject.instructionCheckAndRemove(action: (JSONArray) -> Unit) {
|
||||||
instructionTimelineAddEntries()?.entriesRemoveAnnoyance()
|
instructionTimelineAddEntries()?.let(action)
|
||||||
instructionGetAddEntries()?.entriesRemoveAnnoyance()
|
instructionGetAddEntries()?.let(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// entries
|
// entries
|
||||||
@ -164,14 +164,57 @@ internal object TwiFucker {
|
|||||||
entriesRemoveTweetDetailRelatedTweets()
|
entriesRemoveTweetDetailRelatedTweets()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun JSONObject.entryIsWhoToFollow(): Boolean = optString("entryId").let {
|
||||||
|
it.startsWith("whoToFollow-") || it.startsWith("who-to-follow-") || it.startsWith("connect-module-")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JSONObject.itemContainsPromotedUser(): Boolean =
|
||||||
|
optJSONObject("item")?.optJSONObject("content")
|
||||||
|
?.has("userPromotedMetadata") == true || optJSONObject("item")?.optJSONObject("content")
|
||||||
|
?.optJSONObject("user")
|
||||||
|
?.has("userPromotedMetadata") == true || optJSONObject("item")?.optJSONObject("content")
|
||||||
|
?.optJSONObject("user")?.has("promotedMetadata") == true
|
||||||
|
|
||||||
|
fun JSONArray.entriesRemoveWhoToFollow() {
|
||||||
|
val entryRemoveIndex = mutableListOf<Int>()
|
||||||
|
forEachIndexed { entryIndex, entry ->
|
||||||
|
if (!entry.entryIsWhoToFollow()) return@forEachIndexed
|
||||||
|
|
||||||
|
Log.d("revanced", "Handle whoToFollow $entryIndex $entry")
|
||||||
|
entryRemoveIndex.add(entryIndex)
|
||||||
|
|
||||||
|
val items = entry.entryGetContentItems()
|
||||||
|
val userRemoveIndex = mutableListOf<Int>()
|
||||||
|
items?.forEachIndexed { index, item ->
|
||||||
|
item.itemContainsPromotedUser().let {
|
||||||
|
if (it) {
|
||||||
|
Log.d("revanced", "Handle whoToFollow promoted user $index $item")
|
||||||
|
userRemoveIndex.add(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i in userRemoveIndex.reversed()) {
|
||||||
|
items?.remove(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i in entryRemoveIndex.reversed()) {
|
||||||
|
remove(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun hideRecommendedUsers(json: JSONObject) {
|
fun hideRecommendedUsers(json: JSONObject) {
|
||||||
|
json.filterInstructions { it.entriesRemoveWhoToFollow() }
|
||||||
json.jsonCheckAndRemoveRecommendedUsers()
|
json.jsonCheckAndRemoveRecommendedUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hidePromotedAds(json: JSONObject) {
|
fun hidePromotedAds(json: JSONObject) {
|
||||||
json.jsonGetInstructions()?.forEach { instruction ->
|
json.filterInstructions { it.entriesRemoveAnnoyance() }
|
||||||
instruction.instructionCheckAndRemove()
|
|
||||||
}
|
|
||||||
json.jsonGetData()?.dataCheckAndRemove()
|
json.jsonGetData()?.dataCheckAndRemove()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun JSONObject.filterInstructions(action: (JSONArray) -> Unit) {
|
||||||
|
jsonGetInstructions()?.forEach { instruction ->
|
||||||
|
instruction.instructionCheckAndRemove(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:8.0.0")
|
classpath("com.android.tools.build:gradle:8.0.1")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package com.google.android.libraries.social.licenses;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
|
|
||||||
// Dummy class
|
|
||||||
public final class LicenseActivity extends Activity { }
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.google.android.libraries.youtube.rendering.ui.pivotbar;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.HorizontalScrollView;
|
||||||
|
|
||||||
|
public class PivotBar extends HorizontalScrollView {
|
||||||
|
public PivotBar(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
org.gradle.jvmargs = -Xmx2048m
|
org.gradle.jvmargs = -Xmx2048m
|
||||||
android.useAndroidX = true
|
android.useAndroidX = true
|
||||||
version = 0.107.0
|
version = 0.108.0-dev.24
|
||||||
|
Loading…
x
Reference in New Issue
Block a user