mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-01-02 16:15:58 +01: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)
|
||||
|
||||
|
||||
|
@ -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 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.
|
||||
|
||||
|
@ -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 {
|
||||
//Used by app.revanced.patches.youtube.layout.autorepeat.patch.AutoRepeatPatch
|
||||
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 android.os.Build;
|
||||
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
|
||||
public class CopyVideoUrlPatch {
|
||||
public static void copyUrl(Boolean withTimestamp) {
|
||||
|
||||
public static void copyUrl(boolean withTimestamp) {
|
||||
try {
|
||||
String url = String.format("https://youtu.be/%s", VideoInformation.getVideoId());
|
||||
if (withTimestamp) {
|
||||
long seconds = VideoInformation.getVideoTime() / 1000;
|
||||
url += String.format("?t=%s", seconds);
|
||||
StringBuilder builder = new StringBuilder("https://youtu.be/");
|
||||
builder.append(VideoInformation.getVideoId());
|
||||
final long currentVideoTimeInSeconds = VideoInformation.getVideoTime() / 1000;
|
||||
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.showToastShort(str("share_copy_url_success"));
|
||||
ReVancedUtils.setClipboard(builder.toString());
|
||||
// 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) {
|
||||
LogHelper.printException(() -> "Failed to generate video url", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,10 +4,13 @@ import app.revanced.integrations.settings.SettingsEnum;
|
||||
|
||||
public class DisableAutoCaptionsPatch {
|
||||
|
||||
/**
|
||||
* Used by injected code. Do not delete.
|
||||
*/
|
||||
public static boolean captionsButtonDisabled;
|
||||
|
||||
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 {
|
||||
//Used by app.revanced.patches.youtube.layout.startupshortsreset.patch.DisableShortsOnStartupPatch
|
||||
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) {
|
||||
// do nothing if disabled
|
||||
if (!SettingsEnum.USE_HDR_AUTO_BRIGHTNESS.getBoolean()) {
|
||||
if (!SettingsEnum.HDR_AUTO_BRIGHTNESS.getBoolean()) {
|
||||
return original;
|
||||
}
|
||||
|
||||
|
@ -2,13 +2,12 @@ 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.ReVancedUtils;
|
||||
|
||||
public class HideAlbumCardsPatch {
|
||||
//Used by app.revanced.patches.youtube.layout.hidealbumcards.patch.HideAlbumCardsPatch
|
||||
public static void hideAlbumCards(View view) {
|
||||
public static void hideAlbumCard(View view) {
|
||||
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 app.revanced.integrations.adremover.AdRemoverAPI;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
|
||||
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) {
|
||||
if (!SettingsEnum.HIDE_BREAKING_NEWS.getBoolean()) return;
|
||||
AdRemoverAPI.HideViewWithLayout1dp(view);
|
||||
if (!SettingsEnum.HIDE_BREAKING_NEWS.getBoolean()
|
||||
|| isSpoofingOldVersionWithHorizontalCardListWatchHistory()) return;
|
||||
ReVancedUtils.hideViewByLayoutParams(view);
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,13 @@ 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.ReVancedUtils;
|
||||
|
||||
public class HideCrowdfundingBoxPatch {
|
||||
//Used by app.revanced.patches.youtube.layout.hidecrowdfundingbox.patch.HideCrowdfundingBoxPatch
|
||||
public static void hideCrowdfundingBox(View view) {
|
||||
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.
|
||||
*/
|
||||
public static String enableExternalBrowser(String original) {
|
||||
if (SettingsEnum.ENABLE_EXTERNAL_BROWSER.getBoolean()) original = "";
|
||||
if (SettingsEnum.EXTERNAL_BROWSER.getBoolean()) original = "";
|
||||
return original;
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import app.revanced.integrations.shared.PlayerOverlays;
|
||||
@SuppressWarnings("unused")
|
||||
public class PlayerOverlaysHookPatch {
|
||||
/**
|
||||
* Hook into YouTubePlayerOverlaysLayout.onFinishInflate() method
|
||||
* Injection point.
|
||||
*
|
||||
* @param thisRef reference to the view
|
||||
* @smali YouTubePlayerOverlaysLayout_onFinishInflateHook(Ljava / lang / Object ;)V
|
||||
|
@ -3,32 +3,25 @@ package app.revanced.integrations.patches;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
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")
|
||||
public class PlayerTypeHookPatch {
|
||||
/**
|
||||
* Hook into YouTubePlayerOverlaysLayout.updatePlayerLayout() method
|
||||
*
|
||||
* @param type the new player type
|
||||
* @smali YouTubePlayerOverlaysLayout_updatePlayerTypeHookEX(Ljava/lang/Object;)V
|
||||
* Injection point.
|
||||
*/
|
||||
public static void YouTubePlayerOverlaysLayout_updatePlayerTypeHookEX(@Nullable Object type) {
|
||||
if (type == null) return;
|
||||
public static void setPlayerType(@Nullable Enum<?> youTubePlayerType) {
|
||||
if (youTubePlayerType == null) return;
|
||||
|
||||
// update current player type
|
||||
final PlayerType newType = PlayerType.safeParseFromString(type.toString());
|
||||
if (newType == null) {
|
||||
LogHelper.printException(() -> "Unknown PlayerType encountered: " + type);
|
||||
} else {
|
||||
PlayerType.setCurrent(newType);
|
||||
LogHelper.printDebug(() -> "PlayerType was updated to: " + newType);
|
||||
}
|
||||
PlayerType.setFromString(youTubePlayerType.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
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 android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.shared.PlayerType;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
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 {
|
||||
|
||||
@Nullable
|
||||
private static String currentVideoId;
|
||||
|
||||
|
||||
/**
|
||||
* Resource identifier of old UI dislike button.
|
||||
*/
|
||||
@ -60,7 +76,7 @@ public class ReturnYouTubeDislikePatch {
|
||||
if (oldUIReplacementSpan == null || oldUIReplacementSpan.toString().equals(s.toString())) {
|
||||
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}.
|
||||
*/
|
||||
public static void setOldUILayoutDislikes(int buttonViewResourceId, @NonNull TextView textView) {
|
||||
public static void setOldUILayoutDislikes(int buttonViewResourceId, @Nullable TextView textView) {
|
||||
try {
|
||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()
|
||||
|| buttonViewResourceId != OLD_UI_DISLIKE_BUTTON_RESOURCE_ID) {
|
||||
|| buttonViewResourceId != OLD_UI_DISLIKE_BUTTON_RESOURCE_ID
|
||||
|| textView == null) {
|
||||
return;
|
||||
}
|
||||
LogHelper.printDebug(() -> "setOldUILayoutDislikes");
|
||||
|
||||
if (oldUIOriginalSpan == null) {
|
||||
// Use value of the first instance, as it appears TextViews can be recycled
|
||||
// and might contain dislikes previously added by the patch.
|
||||
@ -96,23 +115,19 @@ public class ReturnYouTubeDislikePatch {
|
||||
textView.removeTextChangedListener(oldUiTextWatcher);
|
||||
textView.addTextChangedListener(oldUiTextWatcher);
|
||||
|
||||
/**
|
||||
* If the patch is changed to include the dislikes button as a parameter to this method,
|
||||
* then if the button is already selected the dislikes could be adjusted using
|
||||
* {@link ReturnYouTubeDislike#setUserVote(Vote)}
|
||||
*/
|
||||
|
||||
updateOldUIDislikesTextView();
|
||||
|
||||
} catch (Exception ex) {
|
||||
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.
|
||||
@ -157,21 +172,153 @@ public class ReturnYouTubeDislikePatch {
|
||||
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.
|
||||
*
|
||||
* Called when a Shorts dislike Spanned is created.
|
||||
*/
|
||||
public static Spanned onShortsComponentCreated(Spanned original) {
|
||||
public static void newVideoLoaded(@NonNull String videoId) {
|
||||
try {
|
||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
||||
return original;
|
||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) return;
|
||||
|
||||
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) {
|
||||
LogHelper.printException(() -> "onShortsComponentCreated failure", ex);
|
||||
LogHelper.printException(() -> "newVideoLoaded failure", ex);
|
||||
}
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -186,10 +333,14 @@ public class ReturnYouTubeDislikePatch {
|
||||
if (!SettingsEnum.RYD_ENABLED.getBoolean()) {
|
||||
return;
|
||||
}
|
||||
if (!SettingsEnum.RYD_SHORTS.getBoolean() && PlayerType.getCurrent().isNoneHiddenOrMinimized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Vote v : Vote.values()) {
|
||||
if (v.value == vote) {
|
||||
ReturnYouTubeDislike.sendVote(v);
|
||||
|
||||
updateOldUIDislikesTextView();
|
||||
return;
|
||||
}
|
||||
|
@ -2,11 +2,8 @@ package app.revanced.integrations.patches;
|
||||
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
|
||||
public class SeekbarTappingPatch {
|
||||
|
||||
//Used by app.revanced.patches.youtube.interaction.seekbar.patch.EnableSeekbarTappingPatch
|
||||
public static boolean isTapSeekingEnabled() {
|
||||
return SettingsEnum.TAP_SEEKING_ENABLED.getBoolean();
|
||||
public final class SeekbarTappingPatch {
|
||||
public static boolean seekbarTappingEnabled() {
|
||||
return SettingsEnum.SEEKBAR_TAPPING.getBoolean();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public class SpoofSignatureVerificationPatch {
|
||||
*/
|
||||
public static String overrideProtobufParameter(String originalValue) {
|
||||
try {
|
||||
if (!SettingsEnum.SIGNATURE_SPOOFING.getBoolean()) {
|
||||
if (!SettingsEnum.SPOOF_SIGNATURE_VERIFICATION.getBoolean()) {
|
||||
return originalValue;
|
||||
}
|
||||
|
||||
@ -101,11 +101,11 @@ public class SpoofSignatureVerificationPatch {
|
||||
}
|
||||
LogHelper.printDebug(() -> "YouTube HTTP status code: " + responseCode);
|
||||
|
||||
if (SettingsEnum.SIGNATURE_SPOOFING.getBoolean()) {
|
||||
if (SettingsEnum.SPOOF_SIGNATURE_VERIFICATION.getBoolean()) {
|
||||
return; // already enabled
|
||||
}
|
||||
|
||||
SettingsEnum.SIGNATURE_SPOOFING.saveValue(true);
|
||||
SettingsEnum.SPOOF_SIGNATURE_VERIFICATION.saveValue(true);
|
||||
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
|
||||
|
||||
@ -130,7 +130,7 @@ public class SpoofSignatureVerificationPatch {
|
||||
* @param sd function is not entirely clear
|
||||
*/
|
||||
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 (ap != lastAp || ah != lastAh || av != lastAv || vs != lastVs || sd != lastSd) {
|
||||
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
|
||||
// depends on Whitelist patch (still needs to be written)
|
||||
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.reflect.Method;
|
||||
import java.util.Objects;
|
||||
|
||||
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.ReVancedUtils;
|
||||
|
||||
@ -16,7 +18,7 @@ public final class VideoInformation {
|
||||
private static final float DEFAULT_YOUTUBE_PLAYBACK_SPEED = 1.0f;
|
||||
private static final String SEEK_METHOD_NAME = "seekTo";
|
||||
|
||||
private static WeakReference<Object> playerController;
|
||||
private static WeakReference<Object> playerControllerRef;
|
||||
private static Method seekMethod;
|
||||
|
||||
@NonNull
|
||||
@ -30,17 +32,17 @@ public final class VideoInformation {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
playerController = new WeakReference<>(thisRef);
|
||||
videoLength = 0;
|
||||
videoTime = -1;
|
||||
|
||||
public static void initialize(@NonNull Object playerController) {
|
||||
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);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Failed to initialize", ex);
|
||||
@ -56,7 +58,6 @@ public final class VideoInformation {
|
||||
if (!videoId.equals(newlyLoadedVideoId)) {
|
||||
LogHelper.printDebug(() -> "New video id: " + newlyLoadedVideoId);
|
||||
videoId = newlyLoadedVideoId;
|
||||
playbackSpeed = DEFAULT_YOUTUBE_PLAYBACK_SPEED;
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +125,7 @@ public final class VideoInformation {
|
||||
|
||||
try {
|
||||
LogHelper.printDebug(() -> "Seeking to " + millisecond);
|
||||
return (Boolean) seekMethod.invoke(playerController.get(), millisecond);
|
||||
return (Boolean) seekMethod.invoke(playerControllerRef.get(), millisecond);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Failed to seek", ex);
|
||||
return false;
|
||||
@ -183,7 +184,12 @@ public final class VideoInformation {
|
||||
* @return If the playback is at the end of the video.
|
||||
*
|
||||
* 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() {
|
||||
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 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() {
|
||||
@Override
|
||||
|
@ -2,7 +2,6 @@ package app.revanced.integrations.patches.playback.quality;
|
||||
|
||||
import static app.revanced.integrations.utils.ReVancedUtils.NetworkType;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -20,12 +19,10 @@ public class RememberVideoQualityPatch {
|
||||
private static final SettingsEnum mobileQualitySetting = SettingsEnum.VIDEO_QUALITY_DEFAULT_MOBILE;
|
||||
|
||||
private static boolean qualityNeedsUpdating;
|
||||
@Nullable
|
||||
private static String currentVideoId;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@ -91,7 +88,7 @@ public class RememberVideoQualityPatch {
|
||||
}
|
||||
}
|
||||
}
|
||||
LogHelper.printDebug(() -> "VideoId: " + currentVideoId + " videoQualities: " + videoQualities);
|
||||
LogHelper.printDebug(() -> "videoQualities: " + videoQualities);
|
||||
}
|
||||
|
||||
if (userChangedDefaultQuality) {
|
||||
@ -113,15 +110,25 @@ public class RememberVideoQualityPatch {
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
m.invoke(qInterface, qualityToUse);
|
||||
return qualityIndexToUse;
|
||||
@ -135,7 +142,7 @@ public class RememberVideoQualityPatch {
|
||||
* Injection point.
|
||||
*/
|
||||
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;
|
||||
userChangedDefaultQuality = true;
|
||||
@ -144,25 +151,9 @@ public class RememberVideoQualityPatch {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void newVideoStarted(@NonNull String videoId) {
|
||||
// The same videoId can be passed in multiple times for a single video playback.
|
||||
// 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);
|
||||
public static void newVideoStarted(Object ignoredPlayerController) {
|
||||
LogHelper.printDebug(() -> "newVideoStarted");
|
||||
qualityNeedsUpdating = true;
|
||||
|
||||
if (!videoId.equals(currentVideoId)) {
|
||||
currentVideoId = videoId;
|
||||
videoQualities = null;
|
||||
}
|
||||
videoQualities = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,103 @@
|
||||
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 {
|
||||
/**
|
||||
* Default playback speeds offered by YouTube.
|
||||
* Values are also used by {@link RememberPlaybackSpeedPatch}.
|
||||
*
|
||||
* If custom video speed is applied,
|
||||
* then this array is overwritten by the patch with custom speeds
|
||||
* Maximum playback speed, exclusive value. Custom speeds must be less than this value.
|
||||
*/
|
||||
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;
|
||||
|
||||
import android.preference.ListPreference;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.integrations.patches.VideoInformation;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
|
||||
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.
|
||||
* Called when a new video loads.
|
||||
*/
|
||||
public static void newVideoLoaded(@NonNull String videoId) {
|
||||
if (videoId.equals(currentVideoId)) {
|
||||
return;
|
||||
}
|
||||
currentVideoId = videoId;
|
||||
public static void newVideoStarted(Object ignoredPlayerController) {
|
||||
LogHelper.printDebug(() -> "newVideoStarted");
|
||||
VideoInformation.overridePlaybackSpeed(SettingsEnum.PLAYBACK_SPEED_DEFAULT.getFloat());
|
||||
}
|
||||
|
||||
@ -38,7 +22,7 @@ public final class RememberPlaybackSpeedPatch {
|
||||
* @param playbackSpeed The playback speed the user selected
|
||||
*/
|
||||
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);
|
||||
ReVancedUtils.showToastLong("Changed default speed to: " + playbackSpeed + "x");
|
||||
}
|
||||
@ -52,26 +36,4 @@ public final class RememberPlaybackSpeedPatch {
|
||||
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 java.text.NumberFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* Must be less than 5 seconds, as per:
|
||||
* 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.
|
||||
@ -59,6 +96,12 @@ public class ReturnYouTubeDislike {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -85,6 +128,13 @@ public class ReturnYouTubeDislike {
|
||||
@GuardedBy("videoIdLockObject")
|
||||
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.
|
||||
*/
|
||||
@ -135,13 +185,25 @@ public class ReturnYouTubeDislike {
|
||||
}
|
||||
}
|
||||
|
||||
private static void setCurrentVideoId(@Nullable String videoId) {
|
||||
public static void setCurrentVideoId(@Nullable String videoId) {
|
||||
synchronized (videoIdLockObject) {
|
||||
if (videoId == null && currentVideoId != null) {
|
||||
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;
|
||||
dislikeDataIsShort = false;
|
||||
userVote = null;
|
||||
voteFetchFuture = null;
|
||||
originalDislikeSpan = null;
|
||||
replacementLikeDislikeSpan = null;
|
||||
@ -154,7 +216,7 @@ public class ReturnYouTubeDislike {
|
||||
public static void clearCache() {
|
||||
synchronized (videoIdLockObject) {
|
||||
if (replacementLikeDislikeSpan != null) {
|
||||
LogHelper.printDebug(() -> "Clearing cache");
|
||||
LogHelper.printDebug(() -> "Clearing replacement spans");
|
||||
}
|
||||
replacementLikeDislikeSpan = null;
|
||||
}
|
||||
@ -177,12 +239,6 @@ public class ReturnYouTubeDislike {
|
||||
public static void newVideoLoaded(@NonNull String 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) {
|
||||
if (videoId.equals(currentVideoId)) {
|
||||
return; // already loaded
|
||||
@ -192,17 +248,23 @@ public class ReturnYouTubeDislike {
|
||||
setCurrentVideoId(null);
|
||||
return;
|
||||
}
|
||||
PlayerType currentPlayerType = PlayerType.getCurrent();
|
||||
LogHelper.printDebug(() -> "New video loaded: " + videoId + " playerType: " + currentPlayerType);
|
||||
setCurrentVideoId(videoId);
|
||||
|
||||
// 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
|
||||
// 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,
|
||||
// as any exceptions are propagated out in the later Future#Get call.
|
||||
RYDCachedFetch entry = futureCache.get(videoId);
|
||||
if (entry != null && entry.futureInProgressOrFinishedSuccessfully()) {
|
||||
LogHelper.printDebug(() -> "Using cached RYD fetch: "+ entry.videoId);
|
||||
voteFetchFuture = entry.future;
|
||||
return;
|
||||
}
|
||||
voteFetchFuture = ReVancedUtils.submitOnBackgroundThread(() -> ReturnYouTubeDislikeApi.fetchVotes(videoId));
|
||||
futureCache.put(videoId, new RYDCachedFetch(voteFetchFuture, videoId));
|
||||
}
|
||||
}
|
||||
|
||||
@ -240,13 +302,28 @@ public class ReturnYouTubeDislike {
|
||||
@NonNull
|
||||
private static Spanned waitForFetchAndUpdateReplacementSpan(@NonNull Spanned oldSpannable, boolean isSegmentedButton) {
|
||||
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) {
|
||||
if (replacementLikeDislikeSpan != null) {
|
||||
if (spansHaveEqualTextAndColor(replacementLikeDislikeSpan, oldSpannable)) {
|
||||
if (originalDislikeSpan != null && replacementLikeDislikeSpan != null) {
|
||||
if (spansHaveEqualTextAndColor(oldSpannable, replacementLikeDislikeSpan)) {
|
||||
LogHelper.printDebug(() -> "Ignoring previously created dislikes span");
|
||||
return oldSpannable;
|
||||
}
|
||||
if (spansHaveEqualTextAndColor(Objects.requireNonNull(originalDislikeSpan), oldSpannable)) {
|
||||
if (spansHaveEqualTextAndColor(oldSpannable, originalDislikeSpan)) {
|
||||
LogHelper.printDebug(() -> "Replacing span with previously created dislike span");
|
||||
return replacementLikeDislikeSpan;
|
||||
}
|
||||
@ -258,31 +335,19 @@ public class ReturnYouTubeDislike {
|
||||
return oldSpannable;
|
||||
}
|
||||
oldSpannable = originalDislikeSpan;
|
||||
} else {
|
||||
originalDislikeSpan = oldSpannable; // most up to date original
|
||||
}
|
||||
}
|
||||
|
||||
// Must block the current thread until fetching is done.
|
||||
// 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;
|
||||
}
|
||||
// No replacement span exist, create it now.
|
||||
|
||||
SpannableString replacement = createDislikeSpan(oldSpannable, isSegmentedButton, votingData);
|
||||
synchronized (videoIdLockObject) {
|
||||
replacementLikeDislikeSpan = replacement;
|
||||
if (userVote != null) {
|
||||
votingData.updateUsingVote(userVote);
|
||||
}
|
||||
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) {
|
||||
LogHelper.printDebug(() -> "UI timed out while waiting for fetch votes to complete"); // show no toast
|
||||
} catch (Exception e) {
|
||||
@ -291,13 +356,22 @@ public class ReturnYouTubeDislike {
|
||||
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) {
|
||||
ReVancedUtils.verifyOnMainThread();
|
||||
Objects.requireNonNull(vote);
|
||||
try {
|
||||
// Must make a local copy of videoId, since it may change between now and when the vote thread runs.
|
||||
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.
|
||||
// Or shorts was loaded with regular video present, then shorts was closed,
|
||||
// and then user voted on the now visible original video.
|
||||
@ -317,27 +391,48 @@ public class ReturnYouTubeDislike {
|
||||
}
|
||||
});
|
||||
|
||||
clearCache(); // UI needs updating
|
||||
|
||||
// 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);
|
||||
setUserVote(vote);
|
||||
} catch (Exception 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.
|
||||
*
|
||||
@ -363,6 +458,7 @@ public class ReturnYouTubeDislike {
|
||||
/**
|
||||
* @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) {
|
||||
if (!isSegmentedButton) {
|
||||
// Simple replacement of 'dislike' with a number/percentage.
|
||||
@ -393,7 +489,7 @@ public class ReturnYouTubeDislike {
|
||||
}
|
||||
|
||||
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()
|
||||
? 0x29AAAAAA // transparent dark gray
|
||||
: 0xFFD9D9D9; // light gray
|
||||
@ -477,12 +573,12 @@ public class ReturnYouTubeDislike {
|
||||
|
||||
private static SpannableString newSpannableWithDislikes(@NonNull Spanned sourceStyling, @NonNull RYDVoteData voteData) {
|
||||
return newSpanUsingStylingOfAnotherSpan(sourceStyling,
|
||||
SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean()
|
||||
SettingsEnum.RYD_DISLIKE_PERCENTAGE.getBoolean()
|
||||
? formatDislikePercentage(voteData.getDislikePercentage())
|
||||
: 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);
|
||||
Object[] spans = sourceStyle.getSpans(0, sourceStyle.length(), Object.class);
|
||||
for (Object span : spans) {
|
||||
|
@ -82,15 +82,12 @@ public final class RYDVoteData {
|
||||
|
||||
public void updateUsingVote(Vote vote) {
|
||||
if (vote == Vote.LIKE) {
|
||||
LogHelper.printDebug(() -> "Increasing like count");
|
||||
likeCount = fetchedLikeCount + 1;
|
||||
dislikeCount = fetchedDislikeCount;
|
||||
} else if (vote == Vote.DISLIKE) {
|
||||
LogHelper.printDebug(() -> "Increasing dislike count");
|
||||
likeCount = fetchedLikeCount;
|
||||
dislikeCount = fetchedDislikeCount + 1;
|
||||
} else if (vote == Vote.LIKE_REMOVE) {
|
||||
LogHelper.printDebug(() -> "Resetting like/dislike to fetched values");
|
||||
likeCount = fetchedLikeCount;
|
||||
dislikeCount = fetchedDislikeCount;
|
||||
} else {
|
||||
|
@ -5,11 +5,13 @@ import static app.revanced.integrations.utils.StringRef.str;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.ProtocolException;
|
||||
@ -22,6 +24,7 @@ import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
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.
|
||||
*/
|
||||
@ -272,12 +284,13 @@ public class ReturnYouTubeDislikeApi {
|
||||
LogHelper.printDebug(() -> "Video has no like/dislikes (video is a YouTube Story?): " + videoId);
|
||||
return null; // do not updated connection statistics
|
||||
} else {
|
||||
LogHelper.printException(() -> "Failed to fetch votes for video: " + videoId + " response code was: " + responseCode,
|
||||
null, str("revanced_ryd_failure_connection_status_code", responseCode));
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||
}
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
} 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) {
|
||||
// should never happen
|
||||
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);
|
||||
return confirmRegistration(userId, solution);
|
||||
}
|
||||
LogHelper.printException(() -> "Failed to register new user: " + userId
|
||||
+ " response code was: " + responseCode); // failed attempt, and ok to log userId
|
||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||
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) {
|
||||
LogHelper.printException(() -> "Failed to register user", ex);
|
||||
LogHelper.printException(() -> "Failed to register user", ex); // should never happen
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -351,19 +367,23 @@ public class ReturnYouTubeDislikeApi {
|
||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||
return null;
|
||||
}
|
||||
String result = null;
|
||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||
String result = Requester.parseJson(connection);
|
||||
result = Requester.parseJson(connection);
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
LogHelper.printDebug(() -> "Registration confirmation successful");
|
||||
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
|
||||
} 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) {
|
||||
LogHelper.printException(() -> "Failed to confirm registration for user: " + userId
|
||||
+ "solution: " + solution, ex);
|
||||
@ -405,10 +425,16 @@ public class ReturnYouTubeDislikeApi {
|
||||
String solution = solvePuzzle(challenge, difficulty);
|
||||
return confirmVote(videoId, userId, solution);
|
||||
}
|
||||
LogHelper.printException(() -> "Failed to send vote for video: " + videoId
|
||||
+ " vote: " + vote + " response code was: " + responseCode);
|
||||
LogHelper.printInfo(() -> "Failed to send vote for video: " + videoId + " vote: " + vote
|
||||
+ " response code was: " + responseCode);
|
||||
handleConnectionError(str("revanced_ryd_failure_connection_status_code", responseCode), null);
|
||||
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) {
|
||||
// should never happen
|
||||
LogHelper.printException(() -> "Failed to send vote for video: " + videoId + " vote: " + vote, ex);
|
||||
}
|
||||
return false;
|
||||
@ -438,23 +464,26 @@ public class ReturnYouTubeDislikeApi {
|
||||
connection.disconnect(); // disconnect, as no more connections will be made for a little while
|
||||
return false;
|
||||
}
|
||||
|
||||
String result = null;
|
||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||
String result = Requester.parseJson(connection);
|
||||
result = Requester.parseJson(connection);
|
||||
if (result.equalsIgnoreCase("true")) {
|
||||
LogHelper.printDebug(() -> "Vote confirm successful for video: " + videoId);
|
||||
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
|
||||
} 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) {
|
||||
LogHelper.printException(() -> "Failed to confirm vote for video: " + videoId
|
||||
+ " solution: " + solution, ex);
|
||||
+ " solution: " + solution, ex); // should never happen
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -503,7 +532,7 @@ public class ReturnYouTubeDislikeApi {
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -519,9 +548,8 @@ public class ReturnYouTubeDislikeApi {
|
||||
|
||||
private static int countLeadingZeroes(byte[] uInt8View) {
|
||||
int zeroes = 0;
|
||||
int value;
|
||||
for (byte b : uInt8View) {
|
||||
value = b & 0xFF;
|
||||
int value = b & 0xFF;
|
||||
if (value == 0) {
|
||||
zeroes += 8;
|
||||
} else {
|
||||
|
@ -1,69 +1,90 @@
|
||||
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.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 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 {
|
||||
//Download Settings
|
||||
DOWNLOADS_BUTTON_SHOWN("revanced_downloads_enabled", BOOLEAN, TRUE),
|
||||
DOWNLOADS_PACKAGE_NAME("revanced_downloads_package_name", STRING, "org.schabi.newpipe" /* NewPipe */, parents(DOWNLOADS_BUTTON_SHOWN)),
|
||||
// External downloader
|
||||
EXTERNAL_DOWNLOADER("revanced_external_downloader", BOOLEAN, TRUE),
|
||||
EXTERNAL_DOWNLOADER_PACKAGE_NAME("revanced_external_downloader_name", STRING,
|
||||
"org.schabi.newpipe" /* NewPipe */, parents(EXTERNAL_DOWNLOADER)),
|
||||
|
||||
// Copy video URL settings
|
||||
COPY_VIDEO_URL_BUTTON_SHOWN("revanced_copy_video_url_enabled", BOOLEAN, TRUE),
|
||||
COPY_VIDEO_URL_TIMESTAMP_BUTTON_SHOWN("revanced_copy_video_url_timestamp_enabled", BOOLEAN, TRUE),
|
||||
// Copy video URL
|
||||
COPY_VIDEO_URL("revanced_copy_video_url", BOOLEAN, FALSE),
|
||||
COPY_VIDEO_URL_TIMESTAMP("revanced_copy_video_url_timestamp", BOOLEAN, TRUE),
|
||||
|
||||
// Video settings
|
||||
OLD_STYLE_VIDEO_QUALITY_PLAYER_SETTINGS("revanced_use_old_style_quality_settings", BOOLEAN, TRUE),
|
||||
VIDEO_QUALITY_REMEMBER_LAST_SELECTED("revanced_remember_video_quality_last_selected", BOOLEAN, TRUE),
|
||||
VIDEO_QUALITY_DEFAULT_WIFI("revanced_default_video_quality_wifi", INTEGER, -2),
|
||||
VIDEO_QUALITY_DEFAULT_MOBILE("revanced_default_video_quality_mobile", INTEGER, -2),
|
||||
PLAYBACK_SPEED_REMEMBER_LAST_SELECTED("revanced_remember_playback_speed_last_selected", BOOLEAN, TRUE),
|
||||
PLAYBACK_SPEED_DEFAULT("revanced_default_playback_speed", FLOAT, 1.0f),
|
||||
// Video
|
||||
HDR_AUTO_BRIGHTNESS("revanced_hdr_auto_brightness", BOOLEAN, TRUE),
|
||||
SHOW_OLD_VIDEO_MENU("revanced_show_old_video_menu", BOOLEAN, TRUE),
|
||||
REMEMBER_VIDEO_QUALITY_LAST_SELECTED("revanced_remember_video_quality_last_selected", BOOLEAN, TRUE),
|
||||
VIDEO_QUALITY_DEFAULT_WIFI("revanced_video_quality_default_wifi", INTEGER, -2),
|
||||
VIDEO_QUALITY_DEFAULT_MOBILE("revanced_video_quality_default_mobile", INTEGER, -2),
|
||||
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 settings
|
||||
//ENABLE_WHITELIST("revanced_whitelist_ads_enabled", BOOLEAN, FALSE),
|
||||
// Whitelist
|
||||
//WHITELIST("revanced_whitelist_ads", BOOLEAN, FALSE), // TODO: Unused currently
|
||||
|
||||
// Ad settings
|
||||
ADREMOVER_BUTTONED_REMOVAL("revanced_adremover_buttoned", BOOLEAN, TRUE),
|
||||
ADREMOVER_CHANNEL_BAR("revanced_hide_channel_bar", BOOLEAN, FALSE),
|
||||
ADREMOVER_CHANNEL_MEMBER_SHELF_REMOVAL("revanced_adremover_channel_member_shelf_removal", BOOLEAN, TRUE),
|
||||
ADREMOVER_CHAPTER_TEASER_REMOVAL("revanced_adremover_chapter_teaser", BOOLEAN, TRUE),
|
||||
ADREMOVER_COMMUNITY_GUIDELINES_REMOVAL("revanced_adremover_community_guidelines", BOOLEAN, TRUE),
|
||||
ADREMOVER_COMMUNITY_POSTS_REMOVAL("revanced_adremover_community_posts_removal", BOOLEAN, FALSE),
|
||||
ADREMOVER_COMPACT_BANNER_REMOVAL("revanced_adremover_compact_banner_removal", BOOLEAN, TRUE),
|
||||
ADREMOVER_CUSTOM_ENABLED("revanced_adremover_custom_enabled", BOOLEAN, FALSE),
|
||||
ADREMOVER_CUSTOM_REMOVAL("revanced_adremover_custom_strings", STRING, "", true, parents(ADREMOVER_CUSTOM_ENABLED)),
|
||||
ADREMOVER_EMERGENCY_BOX_REMOVAL("revanced_adremover_emergency_box_removal", BOOLEAN, TRUE),
|
||||
ADREMOVER_FEED_SURVEY_REMOVAL("revanced_adremover_feed_survey", BOOLEAN, TRUE),
|
||||
ADREMOVER_GENERAL_ADS_REMOVAL("revanced_adremover_ad_removal", BOOLEAN, TRUE),
|
||||
ADREMOVER_GRAY_SEPARATOR("revanced_adremover_separator", BOOLEAN, TRUE),
|
||||
ADREMOVER_HIDE_CHANNEL_GUIDELINES("revanced_adremover_hide_channel_guidelines", BOOLEAN, TRUE),
|
||||
ADREMOVER_HIDE_LATEST_POSTS("revanced_adremover_hide_latest_posts", BOOLEAN, TRUE),
|
||||
ADREMOVER_IMAGE_SHELF("revanced_hide_image_shelf", BOOLEAN, TRUE),
|
||||
ADREMOVER_INFO_PANEL_REMOVAL("revanced_adremover_info_panel", BOOLEAN, TRUE),
|
||||
ADREMOVER_MEDICAL_PANEL_REMOVAL("revanced_adremover_medical_panel", BOOLEAN, TRUE),
|
||||
ADREMOVER_MERCHANDISE_REMOVAL("revanced_adremover_merchandise", BOOLEAN, TRUE),
|
||||
ADREMOVER_MOVIE_REMOVAL("revanced_adremover_movie", BOOLEAN, TRUE),
|
||||
ADREMOVER_PAID_CONTENT_REMOVAL("revanced_adremover_paid_content", BOOLEAN, TRUE),
|
||||
ADREMOVER_QUICK_ACTIONS("revanced_hide_quick_actions", BOOLEAN, FALSE),
|
||||
ADREMOVER_RELATED_VIDEOS("revanced_hide_related_videos", BOOLEAN, FALSE),
|
||||
ADREMOVER_SELF_SPONSOR_REMOVAL("revanced_adremover_self_sponsor", BOOLEAN, TRUE),
|
||||
ADREMOVER_SHORTS_REMOVAL("revanced_adremover_shorts", BOOLEAN, TRUE, true),
|
||||
ADREMOVER_SUBSCRIBERS_COMMUNITY_GUIDELINES_REMOVAL("revanced_adremover_subscribers_community_guidelines_removal", BOOLEAN, TRUE),
|
||||
ADREMOVER_VIEW_PRODUCTS("revanced_adremover_view_products", BOOLEAN, TRUE),
|
||||
ADREMOVER_WEB_SEARCH_RESULTS("revanced_adremover_web_search_result", BOOLEAN, TRUE),
|
||||
VIDEO_ADS_REMOVAL("revanced_video_ads_removal", BOOLEAN, TRUE, true),
|
||||
// Ads
|
||||
HIDE_BUTTONED_ADS("revanced_hide_buttoned_ads", BOOLEAN, TRUE),
|
||||
HIDE_GENERAL_ADS("revanced_hide_general_ads", BOOLEAN, TRUE),
|
||||
HIDE_HIDE_LATEST_POSTS("revanced_hide_latest_posts_ads", BOOLEAN, TRUE),
|
||||
HIDE_PAID_CONTENT("revanced_hide_paid_content_ads", BOOLEAN, TRUE),
|
||||
HIDE_SELF_SPONSOR("revanced_hide_self_sponsor_ads", BOOLEAN, TRUE),
|
||||
HIDE_VIDEO_ADS("revanced_hide_video_ads", BOOLEAN, TRUE, true),
|
||||
CUSTOM_FILTER("revanced_custom_filter", BOOLEAN, FALSE),
|
||||
CUSTOM_FILTER_STRINGS("revanced_custom_filter_strings", STRING, "", true, parents(CUSTOM_FILTER)),
|
||||
|
||||
// Layout
|
||||
HIDE_CHANNEL_BAR("revanced_hide_channel_bar", BOOLEAN, FALSE),
|
||||
HIDE_CHANNEL_MEMBER_SHELF("revanced_hide_channel_member_shelf", BOOLEAN, TRUE),
|
||||
HIDE_CHAPTER_TEASER("revanced_hide_chapter_teaser", BOOLEAN, TRUE),
|
||||
HIDE_COMMUNITY_GUIDELINES("revanced_hide_community_guidelines", BOOLEAN, TRUE),
|
||||
HIDE_COMMUNITY_POSTS("revanced_hide_community_posts", BOOLEAN, FALSE),
|
||||
HIDE_COMPACT_BANNER("revanced_hide_compact_banner", BOOLEAN, TRUE),
|
||||
HIDE_EMERGENCY_BOX("revanced_hide_emergency_box", BOOLEAN, TRUE),
|
||||
HIDE_FEED_SURVEY("revanced_hide_feed_survey", BOOLEAN, TRUE),
|
||||
HIDE_GRAY_SEPARATOR("revanced_hide_gray_separator", BOOLEAN, TRUE),
|
||||
HIDE_HIDE_CHANNEL_GUIDELINES("revanced_hide_channel_guidelines", BOOLEAN, TRUE),
|
||||
HIDE_IMAGE_SHELF("revanced_hide_image_shelf", BOOLEAN, TRUE),
|
||||
HIDE_HIDE_INFO_PANELS("revanced_hide_info_panels", BOOLEAN, TRUE),
|
||||
HIDE_MEDICAL_PANELS("revanced_hide_medical_panels", BOOLEAN, TRUE),
|
||||
HIDE_MERCHANDISE_BANNERS("revanced_hide_merchandise_banners", BOOLEAN, TRUE),
|
||||
HIDE_MOVIES_SECTION("revanced_hide_movies_section", BOOLEAN, TRUE),
|
||||
HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES("revanced_hide_subscribers_community_guidelines", BOOLEAN, TRUE),
|
||||
HIDE_PRODUCTS_BANNER("revanced_hide_products_banner", BOOLEAN, TRUE),
|
||||
HIDE_WEB_SEARCH_RESULTS("revanced_hide_web_search_results", BOOLEAN, TRUE),
|
||||
HIDE_QUICK_ACTIONS("revanced_hide_quick_actions", BOOLEAN, FALSE),
|
||||
HIDE_RELATED_VIDEOS("revanced_hide_related_videos", BOOLEAN, FALSE),
|
||||
|
||||
// Action buttons
|
||||
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_ACTION_BUTTONS("revanced_hide_action_buttons", BOOLEAN, FALSE),
|
||||
|
||||
// Layout settings
|
||||
DISABLE_STARTUP_SHORTS_PLAYER("revanced_startup_shorts_player_enabled", BOOLEAN, FALSE),
|
||||
// Layout
|
||||
DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE),
|
||||
HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true),
|
||||
HIDE_ARTIST_CARDS("revanced_hide_artist_cards", 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_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, 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_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE),
|
||||
HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE),
|
||||
HIDE_FLOATING_MICROPHONE_BUTTON("revanced_hide_floating_microphone_button", BOOLEAN, TRUE, true),
|
||||
HIDE_FULLSCREEN_PANELS("revanced_hide_fullscreen_panels", 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_OVERLAY("revanced_hide_player_overlay", 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_SHORTS_BUTTON("revanced_hide_shorts_button", BOOLEAN, TRUE, 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_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, 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_TARGET("revanced_spoof_app_version_target", STRING, "17.30.35", true, parents(SPOOF_APP_VERSION)),
|
||||
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", 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
|
||||
SIGNATURE_SPOOFING("revanced_spoof_signature_verification", BOOLEAN, TRUE, "revanced_spoof_signature_verification_user_dialog_message"),
|
||||
CAPTIONS_ENABLED("revanced_autocaptions_enabled", BOOLEAN, FALSE),
|
||||
// Misc
|
||||
AUTO_CAPTIONS("revanced_auto_captions", BOOLEAN, FALSE),
|
||||
DISABLE_ZOOM_HAPTICS("revanced_disable_zoom_haptics", BOOLEAN, TRUE),
|
||||
ENABLE_EXTERNAL_BROWSER("revanced_enable_external_browser", BOOLEAN, TRUE, true),
|
||||
PREFERRED_AUTO_REPEAT("revanced_pref_auto_repeat", BOOLEAN, FALSE),
|
||||
TAP_SEEKING_ENABLED("revanced_enable_tap_seeking", BOOLEAN, TRUE),
|
||||
USE_HDR_AUTO_BRIGHTNESS("revanced_pref_hdr_autobrightness", BOOLEAN, TRUE),
|
||||
EXTERNAL_BROWSER("revanced_external_browser", BOOLEAN, TRUE, true),
|
||||
AUTO_REPEAT("revanced_auto_repeat", BOOLEAN, FALSE),
|
||||
SEEKBAR_TAPPING("revanced_seekbar_tapping", BOOLEAN, TRUE),
|
||||
SPOOF_SIGNATURE_VERIFICATION("revanced_spoof_signature_verification", BOOLEAN, TRUE, "revanced_spoof_signature_verification_user_dialog_message"),
|
||||
|
||||
// Swipe controls
|
||||
ENABLE_SWIPE_BRIGHTNESS("revanced_enable_swipe_brightness", BOOLEAN, TRUE),
|
||||
ENABLE_SWIPE_VOLUME("revanced_enable_swipe_volume", BOOLEAN, TRUE),
|
||||
ENABLE_PRESS_TO_SWIPE("revanced_enable_press_to_swipe", BOOLEAN, FALSE,
|
||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
||||
ENABLE_SWIPE_HAPTIC_FEEDBACK("revanced_enable_swipe_haptic_feedback", BOOLEAN, TRUE,
|
||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
||||
SWIPE_MAGNITUDE_THRESHOLD("revanced_swipe_magnitude_threshold", FLOAT, 30f,
|
||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
||||
SWIPE_BRIGHTNESS("revanced_swipe_brightness", BOOLEAN, TRUE),
|
||||
SWIPE_VOLUME("revanced_swipe_volume", BOOLEAN, TRUE),
|
||||
SWIPE_PRESS_TO_ENGAGE("revanced_swipe_press_to_engage", BOOLEAN, FALSE, true,
|
||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||
SWIPE_HAPTIC_FEEDBACK("revanced_swipe_haptic_feedback", BOOLEAN, TRUE,
|
||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||
SWIPE_MAGNITUDE_THRESHOLD("revanced_swipe_threshold", INTEGER, 30,
|
||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||
SWIPE_OVERLAY_BACKGROUND_ALPHA("revanced_swipe_overlay_background_alpha", INTEGER, 127,
|
||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
||||
SWIPE_OVERLAY_TEXT_SIZE("revanced_swipe_overlay_text_size", FLOAT, 22f,
|
||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||
SWIPE_OVERLAY_TEXT_SIZE("revanced_swipe_text_overlay_size", INTEGER, 22,
|
||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||
SWIPE_OVERLAY_TIMEOUT("revanced_swipe_overlay_timeout", LONG, 500L,
|
||||
parents(ENABLE_SWIPE_BRIGHTNESS, ENABLE_SWIPE_VOLUME)),
|
||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
||||
|
||||
// Debug settings
|
||||
DEBUG("revanced_debug_enabled", BOOLEAN, FALSE),
|
||||
DEBUG_STACKTRACE("revanced_debug_stacktrace_enabled", 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"),
|
||||
// Debugging
|
||||
DEBUG("revanced_debug", BOOLEAN, FALSE),
|
||||
DEBUG_STACKTRACE("revanced_debug_stacktrace", BOOLEAN, FALSE, parents(DEBUG)),
|
||||
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_USER_ID("ryd_userId", STRING, "", RETURN_YOUTUBE_DISLIKE),
|
||||
RYD_SHOW_DISLIKE_PERCENTAGE("ryd_show_dislike_percentage", BOOLEAN, FALSE, RETURN_YOUTUBE_DISLIKE, parents(RYD_ENABLED)),
|
||||
RYD_USE_COMPACT_LAYOUT("ryd_use_compact_layout", BOOLEAN, FALSE, RETURN_YOUTUBE_DISLIKE, parents(RYD_ENABLED)),
|
||||
RYD_USER_ID("ryd_user_id", STRING, "", RETURN_YOUTUBE_DISLIKE),
|
||||
RYD_SHORTS("ryd_shorts", BOOLEAN, TRUE, 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
|
||||
SB_ENABLED("sb-enabled", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
||||
SB_VOTING_ENABLED("sb-voting-enabled", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_CREATE_NEW_SEGMENT_ENABLED("sb-new-segment-enabled", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_USE_COMPACT_SKIP_BUTTON("sb-use-compact-skip-button", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_AUTO_HIDE_SKIP_BUTTON("sb-auto-hide-skip-segment-button", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_SHOW_TOAST_ON_SKIP("show-toast", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_TRACK_SKIP_COUNT("count-skips", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_UUID("uuid", STRING, "", SPONSOR_BLOCK),
|
||||
SB_ADJUST_NEW_SEGMENT_STEP("new-segment-step-accuracy", INTEGER, 150, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_MIN_DURATION("sb-min-duration", FLOAT, 0F, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_SEEN_GUIDELINES("sb-seen-gl", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||
SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED("sb-skipped-segments", INTEGER, 0, SPONSOR_BLOCK),
|
||||
SB_SKIPPED_SEGMENTS_TIME_SAVED("sb-skipped-segments-time", LONG, 0L, SPONSOR_BLOCK),
|
||||
SB_SHOW_TIME_WITHOUT_SEGMENTS("sb-length-without-segments", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_IS_VIP("sb-is-vip", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||
SB_LAST_VIP_CHECK("sb-last-vip-check", LONG, 0L, SPONSOR_BLOCK),
|
||||
SB_API_URL("sb-api-host-url", STRING, "https://sponsor.ajay.app", SPONSOR_BLOCK);
|
||||
// SponsorBlock
|
||||
SB_ENABLED("sb_enabled", BOOLEAN, TRUE, SPONSOR_BLOCK),
|
||||
SB_PRIVATE_USER_ID("sb_private_user_id_Do_Not_Share", STRING, "", SPONSOR_BLOCK), /** Do not use directly, instead use {@link SponsorBlockSettings} */
|
||||
DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING("uuid", STRING, "", SPONSOR_BLOCK), // Delete sometime in 2024
|
||||
SB_CREATE_NEW_SEGMENT_STEP("sb_create_new_segment_step", INTEGER, 150, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_VOTING_BUTTON("sb_voting_button", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_CREATE_NEW_SEGMENT("sb_create_new_segment", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_COMPACT_SKIP_BUTTON("sb_compact_skip_button", BOOLEAN, FALSE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_AUTO_HIDE_SKIP_BUTTON("sb_auto_hide_skip_button", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_TOAST_ON_SKIP("sb_toast_on_skip", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_TOAST_ON_CONNECTION_ERROR("sb_toast_on_connection_error", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_TRACK_SKIP_COUNT("sb_track_skip_count", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_SEGMENT_MIN_DURATION("sb_min_segment_duration", FLOAT, 0F, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_VIDEO_LENGTH_WITHOUT_SEGMENTS("sb_video_length_without_segments", BOOLEAN, TRUE, SPONSOR_BLOCK, parents(SB_ENABLED)),
|
||||
SB_API_URL("sb_api_url", STRING, "https://sponsor.ajay.app", SPONSOR_BLOCK),
|
||||
SB_USER_IS_VIP("sb_user_is_vip", BOOLEAN, FALSE, SPONSOR_BLOCK),
|
||||
// SB settings not exported
|
||||
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;
|
||||
}
|
||||
|
||||
@ -206,26 +393,32 @@ public enum SettingsEnum {
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue) {
|
||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, null, null);
|
||||
}
|
||||
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||
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,
|
||||
String userDialogMessage) {
|
||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, userDialogMessage, null);
|
||||
}
|
||||
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||
SettingsEnum[] parents) {
|
||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, false, null, parents);
|
||||
}
|
||||
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||
boolean rebootApp, String userDialogMessage) {
|
||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, userDialogMessage, null);
|
||||
}
|
||||
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||
boolean rebootApp, SettingsEnum[] parents) {
|
||||
this(path, returnType, defaultValue, SharedPrefCategory.YOUTUBE, rebootApp, null, parents);
|
||||
}
|
||||
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue,
|
||||
boolean rebootApp, String userDialogMessage, SettingsEnum[] 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) {
|
||||
this(path, returnType, defaultValue, prefName, false, null, null);
|
||||
}
|
||||
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
||||
boolean rebootApp) {
|
||||
this(path, returnType, defaultValue, prefName, rebootApp, null, null);
|
||||
}
|
||||
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
||||
String userDialogMessage) {
|
||||
this(path, returnType, defaultValue, prefName, false, userDialogMessage, null);
|
||||
}
|
||||
|
||||
SettingsEnum(String path, ReturnType returnType, Object defaultValue, SharedPrefCategory prefName,
|
||||
SettingsEnum[] parents) {
|
||||
this(path, returnType, defaultValue, prefName, false, null, parents);
|
||||
}
|
||||
|
||||
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.returnType = Objects.requireNonNull(returnType);
|
||||
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 {
|
||||
loadAllSettings();
|
||||
|
||||
for (SettingsEnum setting : values()) {
|
||||
pathToSetting.put(setting.path, setting);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static SettingsEnum settingFromPath(@NonNull String str) {
|
||||
for (SettingsEnum setting : values()) {
|
||||
if (setting.path.equals(str)) return setting;
|
||||
}
|
||||
return null;
|
||||
return pathToSetting.get(str);
|
||||
}
|
||||
|
||||
private static void loadAllSettings() {
|
||||
for (SettingsEnum setting : values()) {
|
||||
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() {
|
||||
@ -315,10 +616,10 @@ public enum SettingsEnum {
|
||||
|
||||
/**
|
||||
* Sets, but does _not_ persistently save the value.
|
||||
*
|
||||
* <p>
|
||||
* This intentionally is a static method, to deter accidental usage
|
||||
* when {@link #saveValue(Object)} was intended.
|
||||
*
|
||||
* <p>
|
||||
* This method is only to be used by the Settings preference code.
|
||||
*/
|
||||
public static void setValue(@NonNull SettingsEnum setting, @NonNull String newValue) {
|
||||
@ -343,11 +644,12 @@ public enum SettingsEnum {
|
||||
throw new IllegalStateException(setting.name());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only to be used by the Settings preference code.
|
||||
*/
|
||||
public static void setValue(@NonNull SettingsEnum setting, @NonNull Boolean newValue) {
|
||||
Objects.requireNonNull(newValue);
|
||||
setting.returnType.validate(newValue);
|
||||
setting.value = newValue;
|
||||
}
|
||||
|
||||
@ -355,7 +657,8 @@ public enum SettingsEnum {
|
||||
* Sets the value, and persistently saves it.
|
||||
*/
|
||||
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) {
|
||||
case BOOLEAN:
|
||||
sharedPref.saveBoolean(path, (boolean) newValue);
|
||||
@ -375,12 +678,11 @@ public enum SettingsEnum {
|
||||
default:
|
||||
throw new IllegalStateException(name());
|
||||
}
|
||||
value = newValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if this setting can be configured and used.
|
||||
*
|
||||
* <p>
|
||||
* Not to be confused with {@link #getBoolean()}
|
||||
*/
|
||||
public boolean isAvailable() {
|
||||
@ -393,6 +695,13 @@ public enum SettingsEnum {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return if the currently set value is the same as {@link #defaultValue}
|
||||
*/
|
||||
public boolean isSetToDefault() {
|
||||
return value.equals(defaultValue);
|
||||
}
|
||||
|
||||
public boolean getBoolean() {
|
||||
return (Boolean) value;
|
||||
}
|
||||
@ -422,11 +731,174 @@ public enum SettingsEnum {
|
||||
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 {
|
||||
BOOLEAN,
|
||||
INTEGER,
|
||||
STRING,
|
||||
LONG,
|
||||
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 app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
|
||||
/**
|
||||
@ -35,6 +36,11 @@ public enum SharedPrefCategory {
|
||||
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) {
|
||||
preferences.edit().putString(key, (value == null ? null : value.toString())).apply();
|
||||
}
|
||||
@ -91,7 +97,14 @@ public enum SharedPrefCategory {
|
||||
}
|
||||
return _default;
|
||||
} 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;
|
||||
} 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;
|
||||
} 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;
|
||||
|
||||
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.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.libraries.social.licenses.LicenseActivity;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import app.revanced.integrations.utils.ThemeHelper;
|
||||
|
||||
public class ReVancedSettingActivity {
|
||||
@ -19,76 +20,68 @@ public class ReVancedSettingActivity {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setTheme(LicenseActivity base) {
|
||||
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();
|
||||
}
|
||||
|
||||
public static void initializeSettings(Activity licenseActivity) {
|
||||
try {
|
||||
TextView toolbar = getTextView((ViewGroup) base.findViewById(ReVancedUtils.getResourceIdentifier("toolbar", "id")));
|
||||
if (toolbar == null) {
|
||||
// FIXME
|
||||
// https://github.com/revanced/revanced-patches/issues/1384
|
||||
LogHelper.printDebug(() -> "Could not find toolbar");
|
||||
} else {
|
||||
toolbar.setText(preferenceIdentifier);
|
||||
ThemeHelper.setActivityTheme(licenseActivity);
|
||||
licenseActivity.setContentView(
|
||||
getResourceIdentifier("revanced_settings_with_toolbar", "layout"));
|
||||
setBackButton(licenseActivity);
|
||||
|
||||
PreferenceFragment fragment;
|
||||
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
|
||||
public static <T extends View> T getView(Class<T> typeClass, ViewGroup viewGroup) {
|
||||
if (viewGroup == null) {
|
||||
return null;
|
||||
@SuppressLint("UseCompatLoadingForDrawables")
|
||||
private static void setBackButton(Activity activity) {
|
||||
ViewGroup toolbar = activity.findViewById(getToolbarResourceId());
|
||||
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();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View childAt = viewGroup.getChildAt(i);
|
||||
if (childAt.getClass() == typeClass) {
|
||||
return (T) childAt;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return toolbarResourceId;
|
||||
}
|
||||
|
||||
@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 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.SharedPrefCategory;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
|
||||
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.
|
||||
*/
|
||||
@ -42,33 +69,53 @@ public class ReVancedSettingsFragment extends PreferenceFragment {
|
||||
if (setting == null) {
|
||||
return;
|
||||
}
|
||||
Preference pref = this.findPreference(str);
|
||||
LogHelper.printDebug(() -> "Setting " + setting.name() + " was changed. Preference " + str + ": " + pref);
|
||||
Preference pref = findPreference(str);
|
||||
LogHelper.printDebug(() -> setting.name() + ": " + " setting value:" + setting.getObjectValue() + " pref:" + pref);
|
||||
if (pref == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pref instanceof SwitchPreference) {
|
||||
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) {
|
||||
String editText = ((EditTextPreference) pref).getText();
|
||||
SettingsEnum.setValue(setting, editText);
|
||||
EditTextPreference editPreference = (EditTextPreference) pref;
|
||||
if (settingImportInProgress) {
|
||||
editPreference.getEditText().setText(setting.getObjectValue().toString());
|
||||
} else {
|
||||
SettingsEnum.setValue(setting, editPreference.getText());
|
||||
}
|
||||
} else if (pref instanceof ListPreference) {
|
||||
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);
|
||||
} else {
|
||||
LogHelper.printException(() -> "Setting cannot be handled: " + pref.getClass() + " " + pref);
|
||||
return;
|
||||
}
|
||||
|
||||
enableDisablePreferences();
|
||||
|
||||
if (settingImportInProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showingUserDialogMessage) {
|
||||
if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) {
|
||||
showSettingUserDialogConfirmation(getActivity(), (SwitchPreference) pref, setting);
|
||||
} else if (setting.rebootApp) {
|
||||
rebootDialog(getActivity());
|
||||
showRebootDialog(getActivity());
|
||||
}
|
||||
}
|
||||
|
||||
enableDisablePreferences();
|
||||
} catch (Exception 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
|
||||
Preference defaultSpeedPreference = findPreference(SettingsEnum.PLAYBACK_SPEED_DEFAULT.path);
|
||||
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()) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
|
||||
} 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) {
|
||||
final int entryIndex = listPreference.findIndexOfValue(setting.getObjectValue().toString());
|
||||
String objectStringValue = setting.getObjectValue().toString();
|
||||
final int entryIndex = listPreference.findIndexOfValue(objectStringValue);
|
||||
if (entryIndex >= 0) {
|
||||
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) {
|
||||
showingUserDialogMessage = true;
|
||||
new AlertDialog.Builder(activity)
|
||||
@ -155,7 +190,7 @@ public class ReVancedSettingsFragment extends PreferenceFragment {
|
||||
.setMessage(setting.userDialogMessage.toString())
|
||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
||||
if (setting.rebootApp) {
|
||||
rebootDialog(activity);
|
||||
showRebootDialog(activity);
|
||||
}
|
||||
})
|
||||
.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 {
|
||||
|
||||
/**
|
||||
* If dislikes are shown on Shorts.
|
||||
*/
|
||||
private SwitchPreference shortsPreference;
|
||||
|
||||
/**
|
||||
* If dislikes are shown as percentage.
|
||||
*/
|
||||
@ -29,9 +34,16 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
||||
*/
|
||||
private SwitchPreference compactLayoutPreference;
|
||||
|
||||
/**
|
||||
* If segmented like/dislike button uses smaller compact layout.
|
||||
*/
|
||||
private SwitchPreference toastOnRYDNotAvailable;
|
||||
|
||||
private void updateUIState() {
|
||||
percentagePreference.setEnabled(SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.isAvailable());
|
||||
compactLayoutPreference.setEnabled(SettingsEnum.RYD_USE_COMPACT_LAYOUT.isAvailable());
|
||||
shortsPreference.setEnabled(SettingsEnum.RYD_SHORTS.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
|
||||
@ -58,13 +70,25 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
||||
});
|
||||
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.setChecked(SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.getBoolean());
|
||||
percentagePreference.setChecked(SettingsEnum.RYD_DISLIKE_PERCENTAGE.getBoolean());
|
||||
percentagePreference.setTitle(str("revanced_ryd_dislike_percentage_title"));
|
||||
percentagePreference.setSummaryOn(str("revanced_ryd_dislike_percentage_summary_on"));
|
||||
percentagePreference.setSummaryOff(str("revanced_ryd_dislike_percentage_summary_off"));
|
||||
percentagePreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
SettingsEnum.RYD_SHOW_DISLIKE_PERCENTAGE.saveValue(newValue);
|
||||
SettingsEnum.RYD_DISLIKE_PERCENTAGE.saveValue(newValue);
|
||||
ReturnYouTubeDislike.clearCache();
|
||||
updateUIState();
|
||||
return true;
|
||||
@ -72,18 +96,30 @@ public class ReturnYouTubeDislikeSettingsFragment extends PreferenceFragment {
|
||||
preferenceScreen.addPreference(percentagePreference);
|
||||
|
||||
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.setSummaryOn(str("revanced_ryd_compact_layout_summary_on"));
|
||||
compactLayoutPreference.setSummaryOff(str("revanced_ryd_compact_layout_summary_off"));
|
||||
compactLayoutPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
SettingsEnum.RYD_USE_COMPACT_LAYOUT.saveValue(newValue);
|
||||
SettingsEnum.RYD_COMPACT_LAYOUT.saveValue(newValue);
|
||||
ReturnYouTubeDislike.clearCache();
|
||||
updateUIState();
|
||||
return true;
|
||||
});
|
||||
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();
|
||||
|
||||
|
||||
|
@ -20,6 +20,7 @@ import android.preference.PreferenceScreen;
|
||||
import android.preference.SwitchPreference;
|
||||
import android.text.Html;
|
||||
import android.text.InputType;
|
||||
import android.util.TypedValue;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@ -51,6 +52,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
private SwitchPreference showSkipToast;
|
||||
private SwitchPreference trackSkips;
|
||||
private SwitchPreference showTimeWithoutSegments;
|
||||
private SwitchPreference toastOnConnectionError;
|
||||
|
||||
private EditTextPreference newSegmentStep;
|
||||
private EditTextPreference minSegmentDuration;
|
||||
@ -67,41 +69,44 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
if (!enabled) {
|
||||
SponsorBlockViewController.hideAll();
|
||||
SegmentPlaybackController.setCurrentVideoId(null);
|
||||
} else if (!SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.getBoolean()) {
|
||||
} else if (!SettingsEnum.SB_CREATE_NEW_SEGMENT.getBoolean()) {
|
||||
SponsorBlockViewController.hideNewSegmentLayout();
|
||||
}
|
||||
// voting and add new segment buttons automatically shows/hides themselves
|
||||
|
||||
sbEnabled.setChecked(enabled);
|
||||
|
||||
addNewSegment.setChecked(SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.getBoolean());
|
||||
addNewSegment.setChecked(SettingsEnum.SB_CREATE_NEW_SEGMENT.getBoolean());
|
||||
addNewSegment.setEnabled(enabled);
|
||||
|
||||
votingEnabled.setChecked(SettingsEnum.SB_VOTING_ENABLED.getBoolean());
|
||||
votingEnabled.setChecked(SettingsEnum.SB_VOTING_BUTTON.getBoolean());
|
||||
votingEnabled.setEnabled(enabled);
|
||||
|
||||
compactSkipButton.setChecked(SettingsEnum.SB_USE_COMPACT_SKIP_BUTTON.getBoolean());
|
||||
compactSkipButton.setChecked(SettingsEnum.SB_COMPACT_SKIP_BUTTON.getBoolean());
|
||||
compactSkipButton.setEnabled(enabled);
|
||||
|
||||
autoHideSkipSegmentButton.setChecked(SettingsEnum.SB_AUTO_HIDE_SKIP_BUTTON.getBoolean());
|
||||
autoHideSkipSegmentButton.setEnabled(enabled);
|
||||
|
||||
showSkipToast.setChecked(SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean());
|
||||
showSkipToast.setChecked(SettingsEnum.SB_TOAST_ON_SKIP.getBoolean());
|
||||
showSkipToast.setEnabled(enabled);
|
||||
|
||||
toastOnConnectionError.setChecked(SettingsEnum.SB_TOAST_ON_CONNECTION_ERROR.getBoolean());
|
||||
toastOnConnectionError.setEnabled(enabled);
|
||||
|
||||
trackSkips.setChecked(SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean());
|
||||
trackSkips.setEnabled(enabled);
|
||||
|
||||
showTimeWithoutSegments.setChecked(SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.getBoolean());
|
||||
showTimeWithoutSegments.setChecked(SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.getBoolean());
|
||||
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);
|
||||
|
||||
minSegmentDuration.setText(SettingsEnum.SB_MIN_DURATION.getObjectValue().toString());
|
||||
minSegmentDuration.setText(SettingsEnum.SB_SEGMENT_MIN_DURATION.getObjectValue().toString());
|
||||
minSegmentDuration.setEnabled(enabled);
|
||||
|
||||
privateUserId.setText(SettingsEnum.SB_UUID.getString());
|
||||
privateUserId.setText(SettingsEnum.SB_PRIVATE_USER_ID.getString());
|
||||
privateUserId.setEnabled(enabled);
|
||||
|
||||
apiUrl.setEnabled(enabled);
|
||||
@ -171,7 +176,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
votingEnabled.setSummaryOff(str("sb_enable_voting_sum_off"));
|
||||
category.addPreference(votingEnabled);
|
||||
votingEnabled.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
SettingsEnum.SB_VOTING_ENABLED.saveValue(newValue);
|
||||
SettingsEnum.SB_VOTING_BUTTON.saveValue(newValue);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
@ -182,7 +187,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
compactSkipButton.setSummaryOff(str("sb_enable_compact_skip_button_sum_off"));
|
||||
category.addPreference(compactSkipButton);
|
||||
compactSkipButton.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
SettingsEnum.SB_USE_COMPACT_SKIP_BUTTON.saveValue(newValue);
|
||||
SettingsEnum.SB_COMPACT_SKIP_BUTTON.saveValue(newValue);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
@ -207,7 +212,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
return false;
|
||||
});
|
||||
showSkipToast.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
SettingsEnum.SB_SHOW_TOAST_ON_SKIP.saveValue(newValue);
|
||||
SettingsEnum.SB_TOAST_ON_SKIP.saveValue(newValue);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
@ -218,7 +223,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
showTimeWithoutSegments.setSummaryOn(str("sb_general_time_without_sum_on"));
|
||||
showTimeWithoutSegments.setSummaryOff(str("sb_general_time_without_sum_off"));
|
||||
showTimeWithoutSegments.setOnPreferenceChangeListener((preference1, newValue) -> {
|
||||
SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.saveValue(newValue);
|
||||
SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.saveValue(newValue);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
@ -247,7 +252,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
SettingsEnum.SB_CREATE_NEW_SEGMENT_ENABLED.saveValue(newValue);
|
||||
SettingsEnum.SB_CREATE_NEW_SEGMENT.saveValue(newValue);
|
||||
updateUI();
|
||||
return true;
|
||||
});
|
||||
@ -262,7 +267,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
ReVancedUtils.showToastLong(str("sb_general_adjusting_invalid"));
|
||||
return false;
|
||||
}
|
||||
SettingsEnum.SB_ADJUST_NEW_SEGMENT_STEP.saveValue(newAdjustmentValue);
|
||||
SettingsEnum.SB_CREATE_NEW_SEGMENT_STEP.saveValue(newAdjustmentValue);
|
||||
return true;
|
||||
});
|
||||
category.addPreference(newSegmentStep);
|
||||
@ -282,6 +287,17 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
screen.addPreference(category);
|
||||
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.setTitle(str("sb_general_skipcount"));
|
||||
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.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
|
||||
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;
|
||||
});
|
||||
category.addPreference(minSegmentDuration);
|
||||
@ -312,7 +328,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
ReVancedUtils.showToastLong(str("sb_general_uuid_invalid"));
|
||||
return false;
|
||||
}
|
||||
SettingsEnum.SB_UUID.saveValue(newUUID);
|
||||
SettingsEnum.SB_PRIVATE_USER_ID.saveValue(newUUID);
|
||||
fetchAndDisplayStats();
|
||||
return true;
|
||||
});
|
||||
@ -351,9 +367,22 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
});
|
||||
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.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.getEditText().setText(SponsorBlockSettings.exportSettings());
|
||||
return true;
|
||||
@ -419,6 +448,12 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
private void fetchAndDisplayStats() {
|
||||
try {
|
||||
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());
|
||||
loadingPlaceholderPreference.setEnabled(false);
|
||||
statsCategory.addPreference(loadingPlaceholderPreference);
|
||||
@ -428,6 +463,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
UserStats stats = SBRequester.retrieveUserStats();
|
||||
ReVancedUtils.runOnMainThread(() -> { // get back on main thread to modify UI elements
|
||||
addUserStats(loadingPlaceholderPreference, stats);
|
||||
addLocalUserStats();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@ -450,7 +486,8 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
statsCategory.removeAll();
|
||||
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);
|
||||
statsCategory.addPreference(preference);
|
||||
String userName = stats.userName;
|
||||
@ -482,7 +519,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
statsCategory.addPreference(preference);
|
||||
String formatted = statsNumberOfSegmentsSkippedFormatter.format(stats.segmentCount);
|
||||
preference.setTitle(fromHtml(str("sb_stats_submissions", formatted)));
|
||||
if (stats.segmentCount == 0) {
|
||||
if (stats.totalSegmentCountIncludingIgnored == 0) {
|
||||
preference.setSelectable(false);
|
||||
} else {
|
||||
preference.setOnPreferenceClickListener(preference1 -> {
|
||||
@ -512,7 +549,7 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
|
||||
String stats_saved;
|
||||
String stats_saved_sum;
|
||||
if (stats.segmentCount == 0) {
|
||||
if (stats.totalSegmentCountIncludingIgnored == 0) {
|
||||
stats_saved = str("sb_stats_saved_zero");
|
||||
stats_saved_sum = str("sb_stats_saved_sum_zero");
|
||||
} else {
|
||||
@ -528,34 +565,34 @@ public class SponsorBlockSettingsFragment extends PreferenceFragment {
|
||||
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) {
|
||||
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
|
||||
|
||||
import app.revanced.integrations.utils.Event
|
||||
import app.revanced.integrations.utils.LogHelper
|
||||
|
||||
/**
|
||||
* WatchWhile player type
|
||||
*/
|
||||
@Suppress("unused")
|
||||
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_MAXIMIZED,
|
||||
WATCH_WHILE_FULLSCREEN,
|
||||
WATCH_WHILE_SLIDING_MAXIMIZED_FULLSCREEN,
|
||||
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_FULLSCREEN_DISMISSED,
|
||||
INLINE_MINIMAL, // home feed video playback
|
||||
/**
|
||||
* Home feed video playback.
|
||||
*/
|
||||
INLINE_MINIMAL,
|
||||
VIRTUAL_REALITY_FULLSCREEN,
|
||||
WATCH_WHILE_PICTURE_IN_PICTURE;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* safely parse from a string
|
||||
*
|
||||
* @param name the name to find
|
||||
* @return the enum constant, or null if not found
|
||||
*/
|
||||
|
||||
private val nameToPlayerType = values().associateBy { it.name }
|
||||
|
||||
@JvmStatic
|
||||
fun safeParseFromString(name: String): PlayerType? {
|
||||
return values().firstOrNull { it.name == name }
|
||||
fun setFromString(enumName: String) {
|
||||
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
|
||||
var current
|
||||
get() = currentPlayerType
|
||||
set(value) {
|
||||
private set(value) {
|
||||
currentPlayerType = value
|
||||
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 {
|
||||
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;
|
||||
|
||||
import static app.revanced.integrations.utils.StringRef.str;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.text.TextUtils;
|
||||
import android.util.TypedValue;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
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.settings.SettingsEnum;
|
||||
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.SegmentCategory;
|
||||
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.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.
|
||||
*
|
||||
@ -38,12 +33,12 @@ public class SegmentPlaybackController {
|
||||
* 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.
|
||||
*
|
||||
* 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;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* Value is independent of device dpi.
|
||||
*/
|
||||
@ -102,9 +97,9 @@ public class SegmentPlaybackController {
|
||||
@Nullable
|
||||
private static String timeWithoutSegments;
|
||||
|
||||
private static float sponsorBarLeft = 1f;
|
||||
private static float sponsorBarRight = 1f;
|
||||
private static float sponsorBarThickness = 2f;
|
||||
private static int sponsorBarAbsoluteLeft;
|
||||
private static int sponsorAbsoluteBarRight;
|
||||
private static int sponsorBarThickness;
|
||||
|
||||
@Nullable
|
||||
static SponsorSegment[] getSegments() {
|
||||
@ -177,7 +172,7 @@ public class SegmentPlaybackController {
|
||||
* Injection point.
|
||||
* Initializes SponsorBlock when the video player starts playing a new video.
|
||||
*/
|
||||
public static void initialize(Object _o) {
|
||||
public static void initialize(Object ignoredPlayerController) {
|
||||
try {
|
||||
ReVancedUtils.verifyOnMainThread();
|
||||
SponsorBlockSettings.initialize();
|
||||
@ -235,7 +230,7 @@ public class SegmentPlaybackController {
|
||||
SponsorSegment[] segments = SBRequester.getSegments(videoId);
|
||||
|
||||
ReVancedUtils.runOnMainThread(()-> {
|
||||
if (!videoId.equals(SegmentPlaybackController.currentVideoId)) {
|
||||
if (!videoId.equals(currentVideoId)) {
|
||||
// user changed videos before get segments network call could complete
|
||||
LogHelper.printDebug(() -> "Ignoring segments for prior video: " + videoId);
|
||||
return;
|
||||
@ -522,17 +517,21 @@ public class SegmentPlaybackController {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean videoIsPaused = VideoState.getCurrent() == VideoState.PAUSED;
|
||||
if (!userManuallySkipped) {
|
||||
// check for any smaller embedded segments, and count those as autoskipped
|
||||
final boolean showSkipToast = SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean();
|
||||
for (final SponsorSegment otherSegment : segments) {
|
||||
final boolean showSkipToast = SettingsEnum.SB_TOAST_ON_SKIP.getBoolean();
|
||||
for (final SponsorSegment otherSegment : Objects.requireNonNull(segments)) {
|
||||
if (segmentToSkip.end < otherSegment.start) {
|
||||
break; // no other segments can be contained
|
||||
}
|
||||
if (otherSegment == segmentToSkip ||
|
||||
(otherSegment.category != SegmentCategory.HIGHLIGHT && segmentToSkip.containsSegment(otherSegment))) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -542,7 +541,7 @@ public class SegmentPlaybackController {
|
||||
if (segmentToSkip.category == SegmentCategory.UNSUBMITTED) {
|
||||
removeUnsubmittedSegments();
|
||||
SponsorBlockUtils.setNewSponsorSegmentPreviewed();
|
||||
} else {
|
||||
} else if (!videoIsPaused) {
|
||||
SponsorBlockUtils.sendViewRequestAsync(segmentToSkip);
|
||||
}
|
||||
} 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
|
||||
*/
|
||||
@ -620,42 +605,36 @@ public class SegmentPlaybackController {
|
||||
try {
|
||||
Field field = self.getClass().getDeclaredField("replaceMeWithsetSponsorBarRect");
|
||||
field.setAccessible(true);
|
||||
Rect rect = (Rect) field.get(self);
|
||||
if (rect == null) {
|
||||
LogHelper.printException(() -> "Could not find sponsorblock rect");
|
||||
} else {
|
||||
setSponsorBarAbsoluteLeft(rect.left);
|
||||
setSponsorBarAbsoluteRight(rect.right);
|
||||
}
|
||||
Rect rect = (Rect) Objects.requireNonNull(field.get(self));
|
||||
setSponsorBarAbsoluteLeft(rect);
|
||||
setSponsorBarAbsoluteRight(rect);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "setSponsorBarRect failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void setSponsorBarAbsoluteRight(final Rect rect) {
|
||||
setSponsorBarAbsoluteRight(rect.right);
|
||||
private static void setSponsorBarAbsoluteLeft(Rect rect) {
|
||||
final int left = rect.left;
|
||||
if (sponsorBarAbsoluteLeft != left) {
|
||||
LogHelper.printDebug(() -> "setSponsorBarAbsoluteLeft: " + left);
|
||||
sponsorBarAbsoluteLeft = left;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setSponsorBarAbsoluteRight(final float right) {
|
||||
if (sponsorBarRight != right) {
|
||||
LogHelper.printDebug(() -> String.format("setSponsorBarAbsoluteRight: right=%.2f", right));
|
||||
sponsorBarRight = right;
|
||||
private static void setSponsorBarAbsoluteRight(Rect rect) {
|
||||
final int right = rect.right;
|
||||
if (sponsorAbsoluteBarRight != right) {
|
||||
LogHelper.printDebug(() -> "setSponsorBarAbsoluteRight: " + right);
|
||||
sponsorAbsoluteBarRight = right;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point
|
||||
*/
|
||||
public static void setSponsorBarThickness(final int thickness) {
|
||||
setSponsorBarThickness((float) thickness);
|
||||
}
|
||||
|
||||
public static void setSponsorBarThickness(final float thickness) {
|
||||
public static void setSponsorBarThickness(int thickness) {
|
||||
if (sponsorBarThickness != thickness) {
|
||||
LogHelper.printDebug(() -> String.format("setSponsorBarThickness: %.2f", thickness));
|
||||
LogHelper.printDebug(() -> "setSponsorBarThickness: " + thickness);
|
||||
sponsorBarThickness = thickness;
|
||||
}
|
||||
}
|
||||
@ -665,7 +644,7 @@ public class SegmentPlaybackController {
|
||||
*/
|
||||
public static String appendTimeWithoutSegments(String totalTime) {
|
||||
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)) {
|
||||
// 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
|
||||
@ -679,7 +658,7 @@ public class SegmentPlaybackController {
|
||||
|
||||
private static void calculateTimeWithoutSegments() {
|
||||
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) {
|
||||
timeWithoutSegments = null;
|
||||
return;
|
||||
@ -736,25 +715,23 @@ public class SegmentPlaybackController {
|
||||
*/
|
||||
public static void drawSponsorTimeBars(final Canvas canvas, final float posY) {
|
||||
try {
|
||||
if (sponsorBarThickness < 0.1) return;
|
||||
if (segments == null) return;
|
||||
final long videoLength = VideoInformation.getVideoLength();
|
||||
if (videoLength <= 0) return;
|
||||
|
||||
final float thicknessDiv2 = sponsorBarThickness / 2;
|
||||
final float top = posY - thicknessDiv2;
|
||||
final int thicknessDiv2 = sponsorBarThickness / 2; // rounds down
|
||||
final float top = posY - (sponsorBarThickness - thicknessDiv2);
|
||||
final float bottom = posY + thicknessDiv2;
|
||||
final float absoluteLeft = sponsorBarLeft;
|
||||
final float absoluteRight = sponsorBarRight;
|
||||
final float videoMillisecondsToPixels = (1f / videoLength) * (sponsorAbsoluteBarRight - sponsorBarAbsoluteLeft);
|
||||
final float leftPadding = sponsorBarAbsoluteLeft;
|
||||
|
||||
final float tmp1 = (1f / videoLength) * (absoluteRight - absoluteLeft);
|
||||
for (SponsorSegment segment : segments) {
|
||||
final float left = segment.start * tmp1 + absoluteLeft;
|
||||
final float left = leftPadding + segment.start * videoMillisecondsToPixels;
|
||||
final float right;
|
||||
if (segment.category == SegmentCategory.HIGHLIGHT) {
|
||||
right = left + getHighlightSegmentTimeBarScreenWidth();
|
||||
} else {
|
||||
right = segment.end * tmp1 + absoluteLeft;
|
||||
right = leftPadding + segment.end * videoMillisecondsToPixels;
|
||||
}
|
||||
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 android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Patterns;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@ -21,6 +24,10 @@ import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
|
||||
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) {
|
||||
ReVancedUtils.verifyOnMainThread();
|
||||
@ -66,43 +73,43 @@ public class SponsorBlockSettings {
|
||||
}
|
||||
editor.apply();
|
||||
|
||||
String userID = settingsJson.getString("userID");
|
||||
if (!isValidSBUserId(userID)) {
|
||||
throw new IllegalArgumentException("userId is blank");
|
||||
if (settingsJson.has("userID")) {
|
||||
// User id does not exist if user never voted or created any segments.
|
||||
String userID = settingsJson.getString("userID");
|
||||
if (isValidSBUserId(userID)) {
|
||||
SettingsEnum.SB_PRIVATE_USER_ID.saveValue(userID);
|
||||
}
|
||||
}
|
||||
SettingsEnum.SB_UUID.saveValue(userID);
|
||||
|
||||
SettingsEnum.SB_IS_VIP.saveValue(settingsJson.getBoolean("isVip"));
|
||||
SettingsEnum.SB_SHOW_TOAST_ON_SKIP.saveValue(!settingsJson.getBoolean("dontShowNotice"));
|
||||
SettingsEnum.SB_USER_IS_VIP.saveValue(settingsJson.getBoolean("isVip"));
|
||||
SettingsEnum.SB_TOAST_ON_SKIP.saveValue(!settingsJson.getBoolean("dontShowNotice"));
|
||||
SettingsEnum.SB_TRACK_SKIP_COUNT.saveValue(settingsJson.getBoolean("trackViewCount"));
|
||||
SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.saveValue(settingsJson.getBoolean("showTimeWithSkips"));
|
||||
|
||||
String serverAddress = settingsJson.getString("serverAddress");
|
||||
if (!isValidSBServerAddress(serverAddress)) {
|
||||
throw new IllegalArgumentException(str("sb_api_url_invalid"));
|
||||
if (isValidSBServerAddress(serverAddress)) { // Old versions of ReVanced exported wrong url format
|
||||
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) {
|
||||
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");
|
||||
if (skipCount < 0) {
|
||||
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");
|
||||
if (minutesSaved < 0) {
|
||||
throw new IllegalArgumentException("invalid minutesSaved: " + minutesSaved);
|
||||
}
|
||||
SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.saveValue((long)(minutesSaved * 60 * 1000));
|
||||
} catch (JSONException ex) {
|
||||
// ignore. values were not exported in prior versions of ReVanced
|
||||
SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.saveValue((long) (minutesSaved * 60 * 1000));
|
||||
}
|
||||
|
||||
ReVancedUtils.showToastLong(str("sb_settings_import_successful"));
|
||||
@ -136,15 +143,17 @@ public class SponsorBlockSettings {
|
||||
categorySelectionsArray.put(behaviorObject);
|
||||
}
|
||||
}
|
||||
json.put("userID", SettingsEnum.SB_UUID.getString());
|
||||
json.put("isVip", SettingsEnum.SB_IS_VIP.getBoolean());
|
||||
if (SponsorBlockSettings.userHasSBPrivateId()) {
|
||||
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("dontShowNotice", !SettingsEnum.SB_SHOW_TOAST_ON_SKIP.getBoolean());
|
||||
json.put("showTimeWithSkips", SettingsEnum.SB_SHOW_TIME_WITHOUT_SEGMENTS.getBoolean());
|
||||
json.put("minDuration", SettingsEnum.SB_MIN_DURATION.getFloat());
|
||||
json.put("dontShowNotice", !SettingsEnum.SB_TOAST_ON_SKIP.getBoolean());
|
||||
json.put("showTimeWithSkips", SettingsEnum.SB_VIDEO_LENGTH_WITHOUT_SEGMENTS.getBoolean());
|
||||
json.put("minDuration", SettingsEnum.SB_SEGMENT_MIN_DURATION.getFloat());
|
||||
json.put("trackViewCount", SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean());
|
||||
json.put("skipCount", SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.getInt());
|
||||
json.put("minutesSaved", SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.getLong() / (60f * 1000));
|
||||
json.put("skipCount", SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt());
|
||||
json.put("minutesSaved", SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() / (60f * 1000));
|
||||
|
||||
json.put("categorySelections", categorySelectionsArray);
|
||||
json.put("barTypes", barTypesObject);
|
||||
@ -152,13 +161,59 @@ public class SponsorBlockSettings {
|
||||
return json.toString(2);
|
||||
} catch (Exception ex) {
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
return !userId.isEmpty();
|
||||
return !userId.isEmpty() && userId.length() >= SB_PRIVATE_USER_ID_MINIMUM_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -180,6 +235,29 @@ public class SponsorBlockSettings {
|
||||
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;
|
||||
|
||||
public static void initialize() {
|
||||
@ -188,15 +266,6 @@ public class SponsorBlockSettings {
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ public class SponsorBlockUtils {
|
||||
for (int i = 0; i < voteOptions.length; i++) {
|
||||
SegmentVote voteOption = voteOptions[i];
|
||||
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));
|
||||
} else {
|
||||
items[i] = title;
|
||||
@ -214,20 +214,18 @@ public class SponsorBlockUtils {
|
||||
private static void submitNewSegment() {
|
||||
try {
|
||||
ReVancedUtils.verifyOnMainThread();
|
||||
final String uuid = SettingsEnum.SB_UUID.getString();
|
||||
final long start = newSponsorSegmentStartMillis;
|
||||
final long end = newSponsorSegmentEndMillis;
|
||||
final String videoId = VideoInformation.getVideoId();
|
||||
final long videoLength = VideoInformation.getVideoLength();
|
||||
final SegmentCategory segmentCategory = newUserCreatedSegmentCategory;
|
||||
if (start < 0 || end < 0 || start >= end || videoLength <= 0 || videoId.isEmpty()
|
||||
|| segmentCategory == null || uuid.isEmpty()) {
|
||||
if (start < 0 || end < 0 || start >= end || videoLength <= 0 || videoId.isEmpty() || segmentCategory == null) {
|
||||
LogHelper.printException(() -> "invalid parameters");
|
||||
return;
|
||||
}
|
||||
clearUnsubmittedSegmentTimes();
|
||||
ReVancedUtils.runOnBackgroundThread(() -> {
|
||||
SBRequester.submitSegments(uuid, videoId, segmentCategory.key, start, end, videoLength);
|
||||
SBRequester.submitSegments(videoId, segmentCategory.key, start, end, videoLength);
|
||||
SegmentPlaybackController.executeDownloadSegments(videoId);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@ -380,9 +378,9 @@ public class SponsorBlockUtils {
|
||||
return;
|
||||
}
|
||||
segment.recordedAsSkipped = true;
|
||||
final long totalTimeSkipped = SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.getLong() + segment.length();
|
||||
SettingsEnum.SB_SKIPPED_SEGMENTS_TIME_SAVED.saveValue(totalTimeSkipped);
|
||||
SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.saveValue(SettingsEnum.SB_SKIPPED_SEGMENTS_NUMBER_SKIPPED.getInt() + 1);
|
||||
final long totalTimeSkipped = SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.getLong() + segment.length();
|
||||
SettingsEnum.SB_LOCAL_TIME_SAVED_MILLISECONDS.saveValue(totalTimeSkipped);
|
||||
SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.saveValue(SettingsEnum.SB_LOCAL_TIME_SAVED_NUMBER_SEGMENTS.getInt() + 1);
|
||||
|
||||
if (SettingsEnum.SB_TRACK_SKIP_COUNT.getBoolean()) {
|
||||
ReVancedUtils.runOnBackgroundThread(() -> SBRequester.sendSegmentSkippedViewedRequest(segment));
|
||||
|
@ -16,6 +16,9 @@ import android.text.TextUtils;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
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 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[]{
|
||||
SPONSOR,
|
||||
SELF_PROMO,
|
||||
@ -189,6 +197,8 @@ public enum SegmentCategory {
|
||||
*/
|
||||
@NonNull
|
||||
public CategoryBehaviour behaviour;
|
||||
@NonNull
|
||||
public final CategoryBehaviour defaultBehaviour;
|
||||
|
||||
SegmentCategory(String key, StringRef title, StringRef description,
|
||||
StringRef skipButtonText,
|
||||
@ -213,7 +223,7 @@ public enum SegmentCategory {
|
||||
this.skippedToastBeginning = Objects.requireNonNull(skippedToastBeginning);
|
||||
this.skippedToastMiddle = Objects.requireNonNull(skippedToastMiddle);
|
||||
this.skippedToastEnd = Objects.requireNonNull(skippedToastEnd);
|
||||
this.behaviour = Objects.requireNonNull(defaultBehavior);
|
||||
this.behaviour = this.defaultBehaviour = Objects.requireNonNull(defaultBehavior);
|
||||
this.color = this.defaultColor = defaultColor;
|
||||
this.paint = new Paint();
|
||||
setColor(defaultColor);
|
||||
@ -231,10 +241,13 @@ public enum SegmentCategory {
|
||||
}
|
||||
|
||||
String behaviorString = preferences.getString(key, null);
|
||||
if (behaviorString != null) {
|
||||
if (behaviorString == null) {
|
||||
behaviour = defaultBehaviour;
|
||||
} else {
|
||||
CategoryBehaviour preferenceBehavior = CategoryBehaviour.byStringKey(behaviorString);
|
||||
if (preferenceBehavior == null) {
|
||||
LogHelper.printException(() -> "Unknown behavior: " + behaviorString); // should never happen
|
||||
behaviour = defaultBehaviour;
|
||||
} else {
|
||||
behaviour = preferenceBehavior;
|
||||
}
|
||||
@ -253,6 +266,50 @@ public enum SegmentCategory {
|
||||
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
|
||||
*/
|
||||
@ -300,7 +357,7 @@ public enum SegmentCategory {
|
||||
*/
|
||||
@NonNull
|
||||
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)
|
||||
? skipSponsorTextCompactHighlight
|
||||
: skipSponsorTextCompact;
|
||||
|
@ -17,7 +17,12 @@ public class UserStats {
|
||||
* "User reputation". Unclear how SB determines this value.
|
||||
*/
|
||||
public final float reputation;
|
||||
/**
|
||||
* {@link #segmentCount} plus {@link #ignoredSegmentCount}
|
||||
*/
|
||||
public final int totalSegmentCountIncludingIgnored;
|
||||
public final int segmentCount;
|
||||
public final int ignoredSegmentCount;
|
||||
public final int viewCount;
|
||||
public final double minutesSaved;
|
||||
|
||||
@ -26,6 +31,8 @@ public class UserStats {
|
||||
userName = json.getString("userName");
|
||||
reputation = (float)json.getDouble("reputation");
|
||||
segmentCount = json.getInt("segmentCount");
|
||||
ignoredSegmentCount = json.getInt("ignoredSegmentCount");
|
||||
totalSegmentCountIncludingIgnored = segmentCount + ignoredSegmentCount;
|
||||
viewCount = json.getInt("viewCount");
|
||||
minutesSaved = json.getDouble("minutesSaved");
|
||||
}
|
||||
@ -38,6 +45,7 @@ public class UserStats {
|
||||
+ ", userName='" + userName + '\''
|
||||
+ ", reputation=" + reputation
|
||||
+ ", segmentCount=" + segmentCount
|
||||
+ ", ignoredSegmentCount=" + ignoredSegmentCount
|
||||
+ ", viewCount=" + viewCount
|
||||
+ ", minutesSaved=" + minutesSaved
|
||||
+ '}';
|
||||
|
@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import app.revanced.integrations.requests.Requester;
|
||||
import app.revanced.integrations.requests.Route;
|
||||
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.SponsorSegment;
|
||||
import app.revanced.integrations.sponsorblock.objects.SponsorSegment.SegmentVote;
|
||||
@ -49,6 +49,15 @@ public class 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
|
||||
public static SponsorSegment[] getSegments(@NonNull String videoId) {
|
||||
ReVancedUtils.verifyOffMainThread();
|
||||
@ -59,7 +68,7 @@ public class SBRequester {
|
||||
|
||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||
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++) {
|
||||
JSONObject obj = (JSONObject) responseArray.get(i);
|
||||
JSONArray segment = obj.getJSONArray("segment");
|
||||
@ -88,14 +97,16 @@ public class SBRequester {
|
||||
// no segments are found. a normal response
|
||||
LogHelper.printDebug(() -> "No segments found for video: " + videoId);
|
||||
} else {
|
||||
LogHelper.printException(() -> "getSegments failed with response code: " + responseCode,
|
||||
null, str("sb_sponsorblock_connection_failure_status", responseCode));
|
||||
handleConnectionError(str("sb_sponsorblock_connection_failure_status", responseCode), null);
|
||||
connection.disconnect(); // something went wrong, might as well disconnect
|
||||
}
|
||||
} 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) {
|
||||
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
|
||||
@ -127,15 +138,16 @@ public class SBRequester {
|
||||
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) {
|
||||
ReVancedUtils.verifyOffMainThread();
|
||||
try {
|
||||
String privateUserId = SponsorBlockSettings.getSBPrivateUserID();
|
||||
String start = String.format(Locale.US, TIME_TEMPLATE, startTime / 1000f);
|
||||
String end = String.format(Locale.US, TIME_TEMPLATE, endTime / 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 String messageToToast;
|
||||
@ -161,7 +173,10 @@ public class SBRequester {
|
||||
}
|
||||
ReVancedUtils.showToastLong(messageToToast);
|
||||
} catch (SocketTimeoutException ex) {
|
||||
// Always show, even if show connection toasts is turned off
|
||||
ReVancedUtils.showToastLong(str("sb_submit_failed_timeout"));
|
||||
} catch (IOException ex) {
|
||||
ReVancedUtils.showToastLong(str("sb_submit_failed_unknown_error", 0, ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "failed to submit segments", ex);
|
||||
}
|
||||
@ -196,7 +211,7 @@ public class SBRequester {
|
||||
ReVancedUtils.runOnBackgroundThread(() -> {
|
||||
try {
|
||||
String segmentUuid = segment.UUID;
|
||||
String uuid = SettingsEnum.SB_UUID.getString();
|
||||
String uuid = SponsorBlockSettings.getSBPrivateUserID();
|
||||
HttpURLConnection connection = (voteOption == SegmentVote.CATEGORY_CHANGE)
|
||||
? getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_CATEGORY, uuid, segmentUuid, categoryToVoteFor.key)
|
||||
: getConnectionFromRoute(SBRoutes.VOTE_ON_SEGMENT_QUALITY, uuid, segmentUuid, String.valueOf(voteOption.apiVoteType));
|
||||
@ -216,7 +231,9 @@ public class SBRequester {
|
||||
break;
|
||||
}
|
||||
} 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) {
|
||||
LogHelper.printException(() -> "failed to vote for segment", ex); // should never happen
|
||||
}
|
||||
@ -230,7 +247,7 @@ public class SBRequester {
|
||||
public static UserStats retrieveUserStats() {
|
||||
ReVancedUtils.verifyOffMainThread();
|
||||
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);
|
||||
return stats;
|
||||
} catch (IOException ex) {
|
||||
@ -248,7 +265,7 @@ public class SBRequester {
|
||||
public static String setUsername(@NonNull String username) {
|
||||
ReVancedUtils.verifyOffMainThread();
|
||||
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();
|
||||
String responseMessage = connection.getResponseMessage();
|
||||
if (responseCode == HTTP_STATUS_CODE_SUCCESS) {
|
||||
@ -262,15 +279,18 @@ public class SBRequester {
|
||||
}
|
||||
|
||||
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();
|
||||
if (now < (SettingsEnum.SB_LAST_VIP_CHECK.getLong() + TimeUnit.DAYS.toMillis(3))) {
|
||||
return;
|
||||
}
|
||||
ReVancedUtils.runOnBackgroundThread(() -> {
|
||||
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");
|
||||
SettingsEnum.SB_IS_VIP.saveValue(vip);
|
||||
SettingsEnum.SB_USER_IS_VIP.saveValue(vip);
|
||||
SettingsEnum.SB_LAST_VIP_CHECK.saveValue(now);
|
||||
} catch (IOException ex) {
|
||||
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 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 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 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}");
|
||||
|
@ -3,52 +3,35 @@ package app.revanced.integrations.sponsorblock.ui;
|
||||
import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.patches.VideoInformation;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import app.revanced.integrations.videoplayer.BottomControlButton;
|
||||
|
||||
public class CreateSegmentButtonController {
|
||||
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
|
||||
private static Animation fadeIn;
|
||||
private static Animation fadeOut;
|
||||
private static boolean isShowing;
|
||||
|
||||
/**
|
||||
* injection point
|
||||
*/
|
||||
public static void initialize(Object viewStub) {
|
||||
public static void initialize(View youtubeControlsLayout) {
|
||||
try {
|
||||
LogHelper.printDebug(() -> "initializing new segment button");
|
||||
|
||||
RelativeLayout youtubeControlsLayout = (RelativeLayout) viewStub;
|
||||
String buttonIdentifier = "sb_sponsorblock_button";
|
||||
ImageView imageView = youtubeControlsLayout.findViewById(getResourceIdentifier(buttonIdentifier, "id"));
|
||||
if (imageView == null) {
|
||||
LogHelper.printException(() -> "Couldn't find imageView with \"" + buttonIdentifier + "\"");
|
||||
return;
|
||||
}
|
||||
ImageView imageView = Objects.requireNonNull(youtubeControlsLayout.findViewById(
|
||||
getResourceIdentifier("sb_sponsorblock_button", "id")));
|
||||
imageView.setVisibility(View.GONE);
|
||||
imageView.setOnClickListener(v -> {
|
||||
LogHelper.printDebug(() -> "New segment button clicked");
|
||||
SponsorBlockViewController.toggleNewSegmentLayoutVisibility();
|
||||
});
|
||||
buttonReference = new WeakReference<>(imageView);
|
||||
|
||||
// Animations
|
||||
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);
|
||||
buttonReference = new WeakReference<>(imageView);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "initialize failure", ex);
|
||||
}
|
||||
@ -86,7 +69,7 @@ public class CreateSegmentButtonController {
|
||||
return;
|
||||
}
|
||||
if (!immediate) {
|
||||
iView.startAnimation(fadeIn);
|
||||
iView.startAnimation(BottomControlButton.getButtonFadeIn());
|
||||
}
|
||||
iView.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
@ -95,7 +78,7 @@ public class CreateSegmentButtonController {
|
||||
if (iView.getVisibility() == View.VISIBLE) {
|
||||
iView.clearAnimation();
|
||||
if (!immediate) {
|
||||
iView.startAnimation(fadeOut);
|
||||
iView.startAnimation(BottomControlButton.getButtonFadeOut());
|
||||
}
|
||||
iView.setVisibility(View.GONE);
|
||||
}
|
||||
@ -105,7 +88,7 @@ public class CreateSegmentButtonController {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -53,14 +53,14 @@ public final class NewSegmentLayout extends FrameLayout {
|
||||
initializeButton(
|
||||
context,
|
||||
"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"
|
||||
);
|
||||
|
||||
initializeButton(
|
||||
context,
|
||||
"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"
|
||||
);
|
||||
|
||||
|
@ -51,7 +51,7 @@ public class SponsorBlockViewController {
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void initialize(Object obj) {
|
||||
public static void initialize(ViewGroup viewGroup) {
|
||||
try {
|
||||
LogHelper.printDebug(() -> "initializing");
|
||||
|
||||
@ -64,7 +64,6 @@ public class SponsorBlockViewController {
|
||||
LayoutInflater.from(context).inflate(getResourceIdentifier("inline_sponsor_overlay", "layout"), layout);
|
||||
inlineSponsorOverlayRef = new WeakReference<>(layout);
|
||||
|
||||
ViewGroup viewGroup = (ViewGroup) obj;
|
||||
viewGroup.addView(layout);
|
||||
viewGroup.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
|
||||
@Override
|
||||
@ -214,7 +213,7 @@ public class SponsorBlockViewController {
|
||||
// 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
|
||||
// to be forcefully hidden
|
||||
if (!SettingsEnum.PREFERRED_AUTO_REPEAT.getBoolean()) {
|
||||
if (!SettingsEnum.AUTO_REPEAT.getBoolean()) {
|
||||
CreateSegmentButtonController.hide();
|
||||
VotingButtonController.hide();
|
||||
}
|
||||
|
@ -3,11 +3,10 @@ package app.revanced.integrations.sponsorblock.ui;
|
||||
import static app.revanced.integrations.utils.ReVancedUtils.getResourceIdentifier;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.patches.VideoInformation;
|
||||
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.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import app.revanced.integrations.videoplayer.BottomControlButton;
|
||||
|
||||
public class VotingButtonController {
|
||||
private static WeakReference<ImageView> buttonReference = new WeakReference<>(null);
|
||||
private static Animation fadeIn;
|
||||
private static Animation fadeOut;
|
||||
private static boolean isShowing;
|
||||
|
||||
/**
|
||||
* injection point
|
||||
*/
|
||||
public static void initialize(Object viewStub) {
|
||||
public static void initialize(View youtubeControlsLayout) {
|
||||
try {
|
||||
LogHelper.printDebug(() -> "initializing voting button");
|
||||
RelativeLayout controlsLayout = (RelativeLayout) viewStub;
|
||||
String buttonResourceName = "sb_voting_button";
|
||||
ImageView imageView = controlsLayout.findViewById(getResourceIdentifier(buttonResourceName, "id"));
|
||||
if (imageView == null) {
|
||||
LogHelper.printException(() -> "Couldn't find imageView with \"" + buttonResourceName + "\"");
|
||||
return;
|
||||
}
|
||||
ImageView imageView = Objects.requireNonNull(youtubeControlsLayout.findViewById(
|
||||
getResourceIdentifier("sb_voting_button", "id")));
|
||||
imageView.setVisibility(View.GONE);
|
||||
imageView.setOnClickListener(v -> {
|
||||
SponsorBlockUtils.onVotingClicked(v.getContext());
|
||||
});
|
||||
buttonReference = new WeakReference<>(imageView);
|
||||
|
||||
// Animations
|
||||
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);
|
||||
buttonReference = new WeakReference<>(imageView);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "Unable to set RelativeLayout", ex);
|
||||
}
|
||||
@ -86,7 +71,7 @@ public class VotingButtonController {
|
||||
return;
|
||||
}
|
||||
if (!immediate) {
|
||||
iView.startAnimation(fadeIn);
|
||||
iView.startAnimation(BottomControlButton.getButtonFadeIn());
|
||||
}
|
||||
iView.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
@ -95,7 +80,7 @@ public class VotingButtonController {
|
||||
if (iView.getVisibility() == View.VISIBLE) {
|
||||
iView.clearAnimation();
|
||||
if (!immediate) {
|
||||
iView.startAnimation(fadeOut);
|
||||
iView.startAnimation(BottomControlButton.getButtonFadeOut());
|
||||
}
|
||||
iView.setVisibility(View.GONE);
|
||||
}
|
||||
@ -105,7 +90,7 @@ public class VotingButtonController {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@ -116,7 +101,6 @@ public class VotingButtonController {
|
||||
ReVancedUtils.verifyOnMainThread();
|
||||
View v = buttonReference.get();
|
||||
if (v == null) {
|
||||
LogHelper.printDebug(() -> "Cannot hide voting button (value is null)");
|
||||
return;
|
||||
}
|
||||
v.setVisibility(View.GONE);
|
||||
|
@ -24,13 +24,13 @@ class SwipeControlsConfigurationProvider(
|
||||
* should swipe controls for volume be enabled?
|
||||
*/
|
||||
val enableVolumeControls: Boolean
|
||||
get() = SettingsEnum.ENABLE_SWIPE_VOLUME.boolean
|
||||
get() = SettingsEnum.SWIPE_VOLUME.boolean
|
||||
|
||||
/**
|
||||
* should swipe controls for volume be enabled?
|
||||
*/
|
||||
val enableBrightnessControl: Boolean
|
||||
get() = SettingsEnum.ENABLE_SWIPE_BRIGHTNESS.boolean
|
||||
get() = SettingsEnum.SWIPE_BRIGHTNESS.boolean
|
||||
|
||||
/**
|
||||
* is the video player currently in fullscreen mode?
|
||||
@ -52,14 +52,14 @@ class SwipeControlsConfigurationProvider(
|
||||
* should press-to-swipe be enabled?
|
||||
*/
|
||||
val shouldEnablePressToSwipe: Boolean
|
||||
get() = SettingsEnum.ENABLE_PRESS_TO_SWIPE.boolean
|
||||
get() = SettingsEnum.SWIPE_PRESS_TO_ENGAGE.boolean
|
||||
|
||||
/**
|
||||
* threshold for swipe detection
|
||||
* this may be called rapidly in onScroll, so we have to load it once and then leave it constant
|
||||
*/
|
||||
val swipeMagnitudeThreshold: Float
|
||||
get() = SettingsEnum.SWIPE_MAGNITUDE_THRESHOLD.float
|
||||
val swipeMagnitudeThreshold: Int
|
||||
get() = SettingsEnum.SWIPE_MAGNITUDE_THRESHOLD.int
|
||||
//endregion
|
||||
|
||||
//region overlay adjustments
|
||||
@ -68,7 +68,7 @@ class SwipeControlsConfigurationProvider(
|
||||
* should the overlay enable haptic feedback?
|
||||
*/
|
||||
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
|
||||
@ -79,8 +79,8 @@ class SwipeControlsConfigurationProvider(
|
||||
/**
|
||||
* text size for the overlay, in sp
|
||||
*/
|
||||
val overlayTextSize: Float
|
||||
get() = SettingsEnum.SWIPE_OVERLAY_TEXT_SIZE.float
|
||||
val overlayTextSize: Int
|
||||
get() = SettingsEnum.SWIPE_OVERLAY_TEXT_SIZE.int
|
||||
|
||||
/**
|
||||
* get the background color for text on the overlay, as a color int
|
||||
|
@ -74,7 +74,7 @@ class SwipeControlsOverlayLayout(
|
||||
setColor(config.overlayTextBackgroundColor)
|
||||
}
|
||||
setTextColor(config.overlayForegroundColor)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, config.overlayTextSize)
|
||||
setTextSize(TypedValue.COMPLEX_UNIT_SP, config.overlayTextSize.toFloat())
|
||||
compoundDrawablePadding = compoundIconPadding
|
||||
visibility = GONE
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ public class LogHelper {
|
||||
} else {
|
||||
Log.e(logMessage, messageString, ex);
|
||||
}
|
||||
if (SettingsEnum.DEBUG_SHOW_TOAST_ON_ERROR.getBoolean()) {
|
||||
if (SettingsEnum.DEBUG_TOAST_ON_ERROR.getBoolean()) {
|
||||
String toastMessageToDisplay = (userToastMessage != null)
|
||||
? userToastMessage
|
||||
: outerClassSimpleName + ": " + messageString;
|
||||
|
@ -6,9 +6,15 @@ import android.content.res.Resources;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -22,6 +28,8 @@ import java.util.concurrent.SynchronousQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
|
||||
public class ReVancedUtils {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@ -30,6 +38,35 @@ public class ReVancedUtils {
|
||||
private ReVancedUtils() {
|
||||
} // 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.
|
||||
* All tasks run at max thread priority.
|
||||
@ -97,6 +134,24 @@ public class ReVancedUtils {
|
||||
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() {
|
||||
if (context != null) {
|
||||
return context;
|
||||
@ -117,6 +172,7 @@ public class ReVancedUtils {
|
||||
|
||||
@Nullable
|
||||
private static Boolean isRightToLeftTextLayout;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
NONE,
|
||||
MOBILE,
|
||||
|
@ -1,20 +1,15 @@
|
||||
package app.revanced.integrations.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
|
||||
public class ThemeHelper {
|
||||
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) {
|
||||
final int newOrdinalValue = ((Enum) value).ordinal();
|
||||
if (themeValue != newOrdinalValue) {
|
||||
themeValue = newOrdinalValue;
|
||||
LogHelper.printDebug(() -> "Theme value: " + themeValue);
|
||||
LogHelper.printDebug(() -> "Theme value: " + newOrdinalValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,4 +17,11 @@ public class ThemeHelper {
|
||||
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.animation.Animation;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
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 {
|
||||
private static final Animation fadeIn = ReVancedUtils.getResourceAnimation("fade_in");
|
||||
private static final Animation fadeOut = ReVancedUtils.getResourceAnimation("fade_out");
|
||||
private static final Animation fadeIn;
|
||||
private static final Animation fadeOut;
|
||||
|
||||
private final WeakReference<ImageView> buttonRef;
|
||||
private final SettingsEnum setting;
|
||||
protected boolean isVisible;
|
||||
|
||||
static {
|
||||
// TODO: check if these durations are correct.
|
||||
fadeIn = ReVancedUtils.getResourceAnimation("fade_in");
|
||||
fadeIn.setDuration(ReVancedUtils.getResourceInteger("fade_duration_fast"));
|
||||
|
||||
fadeOut = ReVancedUtils.getResourceAnimation("fade_out");
|
||||
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,
|
||||
@NonNull SettingsEnum booleanSetting, @NonNull View.OnClickListener onClickListener) {
|
||||
@NonNull SettingsEnum booleanSetting, @NonNull View.OnClickListener onClickListener,
|
||||
@Nullable View.OnLongClickListener longClickListener) {
|
||||
LogHelper.printDebug(() -> "Initializing button: " + imageViewButtonId);
|
||||
|
||||
if (booleanSetting.returnType != SettingsEnum.ReturnType.BOOLEAN) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
setting = booleanSetting;
|
||||
|
||||
// Create the button.
|
||||
@ -40,6 +57,9 @@ public abstract class BottomControlButton {
|
||||
ReVancedUtils.getResourceIdentifier(imageViewButtonId, "id")
|
||||
));
|
||||
imageView.setOnClickListener(onClickListener);
|
||||
if (longClickListener != null) {
|
||||
imageView.setOnLongClickListener(longClickListener);
|
||||
}
|
||||
imageView.setVisibility(View.GONE);
|
||||
|
||||
buttonRef = new WeakReference<>(imageView);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package app.revanced.integrations.videoplayer;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@ -16,17 +17,21 @@ public class CopyVideoUrlButton extends BottomControlButton {
|
||||
super(
|
||||
viewGroup,
|
||||
"copy_video_url_button",
|
||||
SettingsEnum.COPY_VIDEO_URL_BUTTON_SHOWN,
|
||||
view -> CopyVideoUrlPatch.copyUrl(false)
|
||||
SettingsEnum.COPY_VIDEO_URL,
|
||||
view -> CopyVideoUrlPatch.copyUrl(false),
|
||||
view -> {
|
||||
CopyVideoUrlPatch.copyUrl(true);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void initializeButton(Object obj) {
|
||||
public static void initializeButton(View view) {
|
||||
try {
|
||||
instance = new CopyVideoUrlButton((ViewGroup) obj);
|
||||
instance = new CopyVideoUrlButton((ViewGroup) view);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "initializeButton failure", ex);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package app.revanced.integrations.videoplayer;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
@ -16,15 +17,19 @@ public class CopyVideoUrlTimestampButton extends BottomControlButton {
|
||||
super(
|
||||
bottomControlsViewGroup,
|
||||
"copy_video_url_timestamp_button",
|
||||
SettingsEnum.COPY_VIDEO_URL_TIMESTAMP_BUTTON_SHOWN,
|
||||
view -> CopyVideoUrlPatch.copyUrl(true)
|
||||
SettingsEnum.COPY_VIDEO_URL_TIMESTAMP,
|
||||
view -> CopyVideoUrlPatch.copyUrl(true),
|
||||
view -> {
|
||||
CopyVideoUrlPatch.copyUrl(false);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void initializeButton(Object bottomControlsViewGroup) {
|
||||
public static void initializeButton(View bottomControlsViewGroup) {
|
||||
try {
|
||||
instance = new CopyVideoUrlTimestampButton((ViewGroup) bottomControlsViewGroup);
|
||||
} catch (Exception ex) {
|
||||
|
@ -21,17 +21,18 @@ public class DownloadButton extends BottomControlButton {
|
||||
super(
|
||||
viewGroup,
|
||||
"download_button",
|
||||
SettingsEnum.DOWNLOADS_BUTTON_SHOWN,
|
||||
DownloadButton::onDownloadClick
|
||||
SettingsEnum.EXTERNAL_DOWNLOADER,
|
||||
DownloadButton::onDownloadClick,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
*/
|
||||
public static void initializeButton(Object obj) {
|
||||
public static void initializeButton(View view) {
|
||||
try {
|
||||
instance = new DownloadButton((ViewGroup) obj);
|
||||
instance = new DownloadButton((ViewGroup) view);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "initializeButton failure", ex);
|
||||
}
|
||||
@ -48,7 +49,7 @@ public class DownloadButton extends BottomControlButton {
|
||||
LogHelper.printDebug(() -> "Download button clicked");
|
||||
|
||||
final var context = view.getContext();
|
||||
var downloaderPackageName = SettingsEnum.DOWNLOADS_PACKAGE_NAME.getString();
|
||||
var downloaderPackageName = SettingsEnum.EXTERNAL_DOWNLOADER_PACKAGE_NAME.getString();
|
||||
|
||||
boolean packageEnabled = false;
|
||||
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 */
|
||||
SHOW_DELETED_MESSAGES("revanced_show_deleted_messages", STRING, "cross-out"),
|
||||
AUTO_CLAIM_CHANNEL_POINTS("revanced_auto_claim_channel_points", BOOLEAN, TRUE),
|
||||
|
||||
/* Misc */
|
||||
DEBUG_MODE("revanced_debug_mode", BOOLEAN, FALSE, true);
|
||||
@ -153,6 +154,14 @@ public enum SettingsEnum {
|
||||
return (String) value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value of this setting as as generic object type.
|
||||
*/
|
||||
@NonNull
|
||||
public Object getObjectValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public enum ReturnType {
|
||||
BOOLEAN,
|
||||
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);
|
||||
|
||||
this.registered = true;
|
||||
|
@ -1,5 +1,6 @@
|
||||
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.stream.StreamUtils
|
||||
import org.json.JSONException
|
||||
@ -7,8 +8,9 @@ import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
object JsonHookPatch {
|
||||
// Additional hooks added by corresponding patch.
|
||||
private val hooks = buildList<JsonHook> {
|
||||
// Modified by corresponding patch.
|
||||
add(DummyHook)
|
||||
}
|
||||
|
||||
@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() {
|
||||
dataGetInstructions()?.forEach { instruction ->
|
||||
instruction.instructionCheckAndRemove()
|
||||
instruction.instructionCheckAndRemove { it.entriesRemoveAnnoyance() }
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,9 +107,9 @@ internal object TwiFucker {
|
||||
private fun JSONObject.instructionGetAddEntries(): JSONArray? =
|
||||
optJSONObject("addEntries")?.optJSONArray("entries")
|
||||
|
||||
private fun JSONObject.instructionCheckAndRemove() {
|
||||
instructionTimelineAddEntries()?.entriesRemoveAnnoyance()
|
||||
instructionGetAddEntries()?.entriesRemoveAnnoyance()
|
||||
private fun JSONObject.instructionCheckAndRemove(action: (JSONArray) -> Unit) {
|
||||
instructionTimelineAddEntries()?.let(action)
|
||||
instructionGetAddEntries()?.let(action)
|
||||
}
|
||||
|
||||
// entries
|
||||
@ -164,14 +164,57 @@ internal object TwiFucker {
|
||||
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) {
|
||||
json.filterInstructions { it.entriesRemoveWhoToFollow() }
|
||||
json.jsonCheckAndRemoveRecommendedUsers()
|
||||
}
|
||||
|
||||
fun hidePromotedAds(json: JSONObject) {
|
||||
json.jsonGetInstructions()?.forEach { instruction ->
|
||||
instruction.instructionCheckAndRemove()
|
||||
}
|
||||
json.filterInstructions { it.entriesRemoveAnnoyance() }
|
||||
json.jsonGetData()?.dataCheckAndRemove()
|
||||
}
|
||||
|
||||
private fun JSONObject.filterInstructions(action: (JSONArray) -> Unit) {
|
||||
jsonGetInstructions()?.forEach { instruction ->
|
||||
instruction.instructionCheckAndRemove(action)
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ buildscript {
|
||||
mavenCentral()
|
||||
}
|
||||
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")
|
||||
|
||||
// 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
|
||||
android.useAndroidX = true
|
||||
version = 0.107.0
|
||||
version = 0.108.0-dev.24
|
||||
|
Loading…
Reference in New Issue
Block a user