mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-11-30 15:52:55 +01:00
refactor: Fix package and code structure (#546)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
parent
e6ac417961
commit
bedcef89a5
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.all.connectivity.wifi.spoof;
|
package app.revanced.integrations.all.connectivity.wifi.spoof;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.all.screencapture.removerestriction;
|
package app.revanced.integrations.all.screencapture.removerestriction;
|
||||||
|
|
||||||
import android.media.AudioAttributes;
|
import android.media.AudioAttributes;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.all.screenshot.removerestriction;
|
package app.revanced.integrations.all.screenshot.removerestriction;
|
||||||
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
@ -1,10 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
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.AUTO_REPEAT.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public final class ChangeStartPagePatch {
|
|
||||||
public static void changeIntent(Intent intent) {
|
|
||||||
final var startPage = SettingsEnum.START_PAGE.getString();
|
|
||||||
if (startPage.isEmpty()) return;
|
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Changing start page to " + startPage);
|
|
||||||
intent.setAction("com.google.android.youtube.action." + startPage);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
public class CustomPlayerOverlayOpacityPatch {
|
|
||||||
private static final int DEFAULT_OPACITY = (int) SettingsEnum.PLAYER_OVERLAY_OPACITY.defaultValue;
|
|
||||||
|
|
||||||
public static void changeOpacity(ImageView imageView) {
|
|
||||||
int opacity = SettingsEnum.PLAYER_OVERLAY_OPACITY.getInt();
|
|
||||||
|
|
||||||
if (opacity < 0 || opacity > 100) {
|
|
||||||
ReVancedUtils.showToastLong("Player overlay opacity must be between 0-100");
|
|
||||||
SettingsEnum.PLAYER_OVERLAY_OPACITY.saveValue(DEFAULT_OPACITY);
|
|
||||||
opacity = DEFAULT_OPACITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
imageView.setImageAlpha((opacity * 255) / 100);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
/** @noinspection unused*/
|
|
||||||
public final class DisableFullscreenAmbientModePatch {
|
|
||||||
public static boolean enableFullScreenAmbientMode() {
|
|
||||||
return !SettingsEnum.DISABLE_FULLSCREEN_AMBIENT_MODE.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class DisableRollingNumberAnimationsPatch {
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean disableRollingNumberAnimations() {
|
|
||||||
return SettingsEnum.DISABLE_ROLLING_NUMBER_ANIMATIONS.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public final class EnableTabletLayoutPatch {
|
|
||||||
public static boolean enableTabletLayout() {
|
|
||||||
return SettingsEnum.TABLET_LAYOUT.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class FullscreenPanelsRemoverPatch {
|
|
||||||
public static int getFullscreenPanelsVisibility() {
|
|
||||||
return SettingsEnum.HIDE_FULLSCREEN_PANELS.getBoolean() ? View.GONE : View.VISIBLE;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
public class HideAlbumCardsPatch {
|
|
||||||
public static void hideAlbumCard(View view) {
|
|
||||||
if (!SettingsEnum.HIDE_ALBUM_CARDS.getBoolean()) return;
|
|
||||||
ReVancedUtils.hideViewByLayoutParams(view);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class HideAutoplayButtonPatch {
|
|
||||||
public static boolean isButtonShown() {
|
|
||||||
return !SettingsEnum.HIDE_AUTOPLAY_BUTTON.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.widget.ImageView;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
|
|
||||||
public class HideCaptionsButtonPatch {
|
|
||||||
//Used by app.revanced.patches.youtube.layout.hidecaptionsbutton.patch.HideCaptionsButtonPatch
|
|
||||||
public static void hideCaptionsButton(ImageView imageView) {
|
|
||||||
imageView.setVisibility(SettingsEnum.HIDE_CAPTIONS_BUTTON.getBoolean() ? ImageView.GONE : ImageView.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
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;
|
|
||||||
ReVancedUtils.hideViewByLayoutParams(view);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public final class HideFloatingMicrophoneButtonPatch {
|
|
||||||
public static boolean hideFloatingMicrophoneButton(final boolean original) {
|
|
||||||
return SettingsEnum.HIDE_FLOATING_MICROPHONE_BUTTON.getBoolean() || original;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class HideGetPremiumPatch {
|
|
||||||
/**
|
|
||||||
* Injection point.
|
|
||||||
*/
|
|
||||||
public static boolean hideGetPremiumView() {
|
|
||||||
return SettingsEnum.HIDE_GET_PREMIUM.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class HideInfoCardsPatch {
|
|
||||||
public static void hideInfoCardsIncognito(View view) {
|
|
||||||
if (!SettingsEnum.HIDE_INFO_CARDS.getBoolean()) return;
|
|
||||||
view.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hideInfoCardsMethodCall() {
|
|
||||||
return SettingsEnum.HIDE_INFO_CARDS.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class HideSeekbarPatch {
|
|
||||||
public static boolean hideSeekbar() {
|
|
||||||
return SettingsEnum.HIDE_SEEKBAR.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class HideTimestampPatch {
|
|
||||||
public static boolean hideTimestamp() {
|
|
||||||
return SettingsEnum.HIDE_TIMESTAMP.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public final class RestoreOldSeekbarThumbnailsPatch {
|
|
||||||
public static boolean useFullscreenSeekbarThumbnails() {
|
|
||||||
return !SettingsEnum.RESTORE_OLD_SEEKBAR_THUMBNAILS.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public final class SeekbarTappingPatch {
|
|
||||||
public static boolean seekbarTappingEnabled() {
|
|
||||||
return SettingsEnum.SEEKBAR_TAPPING.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public final class SlideToSeekPatch {
|
|
||||||
public static boolean isSlideToSeekDisabled() {
|
|
||||||
return !SettingsEnum.SLIDE_TO_SEEK.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class TabletMiniPlayerOverridePatch {
|
|
||||||
|
|
||||||
public static boolean getTabletMiniPlayerOverride(boolean original) {
|
|
||||||
if (SettingsEnum.USE_TABLET_MINIPLAYER.getBoolean())
|
|
||||||
return true;
|
|
||||||
return original;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public final class WideSearchbarPatch {
|
|
||||||
public static boolean enableWideSearchbar() {
|
|
||||||
return SettingsEnum.WIDE_SEARCHBAR.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package app.revanced.integrations.patches;
|
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
public class ZoomHapticsPatch {
|
|
||||||
public static boolean shouldVibrate() {
|
|
||||||
return !SettingsEnum.DISABLE_ZOOM_HAPTICS.getBoolean();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.reddit.patches;
|
package app.revanced.integrations.reddit.patches;
|
||||||
|
|
||||||
import com.reddit.domain.model.ILink;
|
import com.reddit.domain.model.ILink;
|
||||||
|
|
@ -1,762 +0,0 @@
|
|||||||
package app.revanced.integrations.settings;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import app.revanced.integrations.sponsorblock.SponsorBlockSettings;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
import app.revanced.integrations.utils.StringRef;
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
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 app.revanced.integrations.utils.StringRef.str;
|
|
||||||
import static java.lang.Boolean.FALSE;
|
|
||||||
import static java.lang.Boolean.TRUE;
|
|
||||||
|
|
||||||
|
|
||||||
public enum SettingsEnum {
|
|
||||||
// External downloader
|
|
||||||
EXTERNAL_DOWNLOADER("revanced_external_downloader", BOOLEAN, FALSE),
|
|
||||||
EXTERNAL_DOWNLOADER_PACKAGE_NAME("revanced_external_downloader_name", STRING,
|
|
||||||
"org.schabi.newpipe" /* NewPipe */, parents(EXTERNAL_DOWNLOADER)),
|
|
||||||
|
|
||||||
// Copy video URL
|
|
||||||
COPY_VIDEO_URL("revanced_copy_video_url", BOOLEAN, FALSE),
|
|
||||||
COPY_VIDEO_URL_TIMESTAMP("revanced_copy_video_url_timestamp", BOOLEAN, TRUE),
|
|
||||||
|
|
||||||
// Video
|
|
||||||
HDR_AUTO_BRIGHTNESS("revanced_hdr_auto_brightness", BOOLEAN, TRUE),
|
|
||||||
@Deprecated SHOW_OLD_VIDEO_QUALITY_MENU("revanced_show_old_video_quality_menu", BOOLEAN, TRUE),
|
|
||||||
RESTORE_OLD_VIDEO_QUALITY_MENU("revanced_restore_old_video_quality_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),
|
|
||||||
|
|
||||||
// Ads
|
|
||||||
HIDE_FULLSCREEN_ADS("revanced_hide_fullscreen_ads", BOOLEAN, TRUE),
|
|
||||||
HIDE_BUTTONED_ADS("revanced_hide_buttoned_ads", BOOLEAN, TRUE),
|
|
||||||
HIDE_GENERAL_ADS("revanced_hide_general_ads", BOOLEAN, TRUE),
|
|
||||||
HIDE_GET_PREMIUM("revanced_hide_get_premium", BOOLEAN, TRUE),
|
|
||||||
HIDE_HIDE_LATEST_POSTS("revanced_hide_latest_posts_ads", BOOLEAN, TRUE),
|
|
||||||
HIDE_MERCHANDISE_BANNERS("revanced_hide_merchandise_banners", BOOLEAN, TRUE),
|
|
||||||
HIDE_PAID_CONTENT("revanced_hide_paid_content_ads", BOOLEAN, TRUE),
|
|
||||||
HIDE_PRODUCTS_BANNER("revanced_hide_products_banner", BOOLEAN, TRUE),
|
|
||||||
HIDE_SHOPPING_LINKS("revanced_hide_shopping_links", BOOLEAN, TRUE),
|
|
||||||
HIDE_SELF_SPONSOR("revanced_hide_self_sponsor_ads", BOOLEAN, TRUE),
|
|
||||||
HIDE_VIDEO_ADS("revanced_hide_video_ads", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_WEB_SEARCH_RESULTS("revanced_hide_web_search_results", BOOLEAN, TRUE),
|
|
||||||
|
|
||||||
// Layout
|
|
||||||
ALT_THUMBNAIL_STILLS("revanced_alt_thumbnail_stills", BOOLEAN, FALSE),
|
|
||||||
ALT_THUMBNAIL_STILLS_TIME("revanced_alt_thumbnail_stills_time", INTEGER, 2, parents(ALT_THUMBNAIL_STILLS)),
|
|
||||||
ALT_THUMBNAIL_STILLS_FAST("revanced_alt_thumbnail_stills_fast", BOOLEAN, FALSE, parents(ALT_THUMBNAIL_STILLS)),
|
|
||||||
ALT_THUMBNAIL_DEARROW("revanced_alt_thumbnail_dearrow", BOOLEAN, false),
|
|
||||||
ALT_THUMBNAIL_DEARROW_API_URL("revanced_alt_thumbnail_dearrow_api_url", STRING,
|
|
||||||
"https://dearrow-thumb.ajay.app/api/v1/getThumbnail", true, parents(ALT_THUMBNAIL_DEARROW)),
|
|
||||||
ALT_THUMBNAIL_DEARROW_CONNECTION_TOAST("revanced_alt_thumbnail_dearrow_connection_toast", BOOLEAN, TRUE, parents(ALT_THUMBNAIL_DEARROW)),
|
|
||||||
CUSTOM_FILTER("revanced_custom_filter", BOOLEAN, FALSE),
|
|
||||||
CUSTOM_FILTER_STRINGS("revanced_custom_filter_strings", STRING, "", true, parents(CUSTOM_FILTER)),
|
|
||||||
DISABLE_FULLSCREEN_AMBIENT_MODE("revanced_disable_fullscreen_ambient_mode", BOOLEAN, TRUE, true),
|
|
||||||
DISABLE_RESUMING_SHORTS_PLAYER("revanced_disable_resuming_shorts_player", BOOLEAN, FALSE),
|
|
||||||
DISABLE_ROLLING_NUMBER_ANIMATIONS("revanced_disable_rolling_number_animations", BOOLEAN, FALSE),
|
|
||||||
DISABLE_SUGGESTED_VIDEO_END_SCREEN("revanced_disable_suggested_video_end_screen", BOOLEAN, TRUE),
|
|
||||||
GRADIENT_LOADING_SCREEN("revanced_gradient_loading_screen", BOOLEAN, FALSE),
|
|
||||||
HIDE_ALBUM_CARDS("revanced_hide_album_cards", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_ARTIST_CARDS("revanced_hide_artist_cards", BOOLEAN, FALSE),
|
|
||||||
HIDE_AUTOPLAY_BUTTON("revanced_hide_autoplay_button", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_BREAKING_NEWS("revanced_hide_breaking_news", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_CAPTIONS_BUTTON("revanced_hide_captions_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_CAST_BUTTON("revanced_hide_cast_button", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_CHANNEL_BAR("revanced_hide_channel_bar", BOOLEAN, FALSE),
|
|
||||||
HIDE_CHANNEL_MEMBER_SHELF("revanced_hide_channel_member_shelf", BOOLEAN, TRUE),
|
|
||||||
HIDE_CHIPS_SHELF("revanced_hide_chips_shelf", BOOLEAN, TRUE),
|
|
||||||
HIDE_COMMENTS_SECTION("revanced_hide_comments_section", BOOLEAN, FALSE, 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_CREATE_BUTTON("revanced_hide_create_button", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_CROWDFUNDING_BOX("revanced_hide_crowdfunding_box", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_EMAIL_ADDRESS("revanced_hide_email_address", BOOLEAN, FALSE),
|
|
||||||
HIDE_EMERGENCY_BOX("revanced_hide_emergency_box", BOOLEAN, TRUE),
|
|
||||||
HIDE_ENDSCREEN_CARDS("revanced_hide_endscreen_cards", BOOLEAN, TRUE),
|
|
||||||
HIDE_EXPANDABLE_CHIP("revanced_hide_expandable_chip", BOOLEAN, TRUE),
|
|
||||||
HIDE_FEED_SURVEY("revanced_hide_feed_survey", BOOLEAN, TRUE),
|
|
||||||
HIDE_FILTER_BAR_FEED_IN_FEED("revanced_hide_filter_bar_feed_in_feed", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_FILTER_BAR_FEED_IN_RELATED_VIDEOS("revanced_hide_filter_bar_feed_in_related_videos", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_FILTER_BAR_FEED_IN_SEARCH("revanced_hide_filter_bar_feed_in_search", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_FLOATING_MICROPHONE_BUTTON("revanced_hide_floating_microphone_button", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_FULLSCREEN_PANELS("revanced_hide_fullscreen_panels", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_GRAY_SEPARATOR("revanced_hide_gray_separator", BOOLEAN, TRUE),
|
|
||||||
HIDE_HIDE_CHANNEL_GUIDELINES("revanced_hide_channel_guidelines", BOOLEAN, TRUE),
|
|
||||||
HIDE_HIDE_INFO_PANELS("revanced_hide_info_panels", BOOLEAN, TRUE),
|
|
||||||
HIDE_HOME_BUTTON("revanced_hide_home_button", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_IMAGE_SHELF("revanced_hide_image_shelf", BOOLEAN, TRUE),
|
|
||||||
HIDE_INFO_CARDS("revanced_hide_info_cards", BOOLEAN, TRUE),
|
|
||||||
HIDE_JOIN_MEMBERSHIP_BUTTON("revanced_hide_join_membership_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_LOAD_MORE_BUTTON("revanced_hide_load_more_button", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_MEDICAL_PANELS("revanced_hide_medical_panels", BOOLEAN, TRUE),
|
|
||||||
HIDE_MIX_PLAYLISTS("revanced_hide_mix_playlists", BOOLEAN, TRUE),
|
|
||||||
HIDE_MOVIES_SECTION("revanced_hide_movies_section", BOOLEAN, TRUE),
|
|
||||||
HIDE_NOTIFY_ME_BUTTON("revanced_hide_notify_me_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_PLAYER_BUTTONS("revanced_hide_player_buttons", BOOLEAN, FALSE),
|
|
||||||
HIDE_PREVIEW_COMMENT("revanced_hide_preview_comment", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_QUICK_ACTIONS("revanced_hide_quick_actions", BOOLEAN, FALSE),
|
|
||||||
HIDE_RELATED_VIDEOS("revanced_hide_related_videos", BOOLEAN, FALSE),
|
|
||||||
HIDE_SEARCH_RESULT_SHELF_HEADER("revanced_hide_search_result_shelf_header", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHORTS_BUTTON("revanced_hide_shorts_button", BOOLEAN, TRUE, true),
|
|
||||||
HIDE_SUBSCRIBERS_COMMUNITY_GUIDELINES("revanced_hide_subscribers_community_guidelines", BOOLEAN, TRUE),
|
|
||||||
HIDE_SUBSCRIPTIONS_BUTTON("revanced_hide_subscriptions_button", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_TIMED_REACTIONS("revanced_hide_timed_reactions", BOOLEAN, TRUE),
|
|
||||||
HIDE_TIMESTAMP("revanced_hide_timestamp", BOOLEAN, FALSE),
|
|
||||||
@Deprecated HIDE_VIDEO_WATERMARK("revanced_hide_video_watermark", BOOLEAN, TRUE),
|
|
||||||
HIDE_VIDEO_CHANNEL_WATERMARK("revanced_hide_channel_watermark", BOOLEAN, TRUE),
|
|
||||||
HIDE_FOR_YOU_SHELF("revanced_hide_for_you_shelf", BOOLEAN, TRUE),
|
|
||||||
HIDE_VIDEO_QUALITY_MENU_FOOTER("revanced_hide_video_quality_menu_footer", BOOLEAN, TRUE),
|
|
||||||
HIDE_SEARCH_RESULT_RECOMMENDATIONS("revanced_hide_search_result_recommendations", BOOLEAN, TRUE),
|
|
||||||
PLAYER_OVERLAY_OPACITY("revanced_player_overlay_opacity", INTEGER, 100, true),
|
|
||||||
PLAYER_POPUP_PANELS("revanced_hide_player_popup_panels", BOOLEAN, FALSE),
|
|
||||||
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.08.35", true, parents(SPOOF_APP_VERSION)),
|
|
||||||
SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON("revanced_switch_create_with_notifications_button", BOOLEAN, TRUE, true),
|
|
||||||
TABLET_LAYOUT("revanced_tablet_layout", BOOLEAN, FALSE, true, "revanced_tablet_layout_user_dialog_message"),
|
|
||||||
USE_TABLET_MINIPLAYER("revanced_tablet_miniplayer", BOOLEAN, FALSE, true),
|
|
||||||
WIDE_SEARCHBAR("revanced_wide_searchbar", BOOLEAN, FALSE, true),
|
|
||||||
START_PAGE("revanced_start_page", STRING, ""),
|
|
||||||
|
|
||||||
// Description
|
|
||||||
HIDE_CHAPTERS("revanced_hide_chapters", BOOLEAN, TRUE),
|
|
||||||
HIDE_INFO_CARDS_SECTION("revanced_hide_info_cards_section", BOOLEAN, TRUE),
|
|
||||||
HIDE_GAME_SECTION("revanced_hide_game_section", BOOLEAN, TRUE),
|
|
||||||
HIDE_MUSIC_SECTION("revanced_hide_music_section", BOOLEAN, TRUE),
|
|
||||||
HIDE_PODCAST_SECTION("revanced_hide_podcast_section", BOOLEAN, TRUE),
|
|
||||||
HIDE_TRANSCIPT_SECTION("revanced_hide_transcript_section", BOOLEAN, TRUE),
|
|
||||||
|
|
||||||
// Shorts
|
|
||||||
HIDE_SHORTS("revanced_hide_shorts", BOOLEAN, FALSE, true),
|
|
||||||
HIDE_SHORTS_JOIN_BUTTON("revanced_hide_shorts_join_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_SHORTS_SUBSCRIBE_BUTTON("revanced_hide_shorts_subscribe_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_SHORTS_SUBSCRIBE_BUTTON_PAUSED("revanced_hide_shorts_subscribe_button_paused", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHORTS_THANKS_BUTTON("revanced_hide_shorts_thanks_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_SHORTS_COMMENTS_BUTTON("revanced_hide_shorts_comments_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHORTS_REMIX_BUTTON("revanced_hide_shorts_remix_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_SHORTS_SHARE_BUTTON("revanced_hide_shorts_share_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHORTS_INFO_PANEL("revanced_hide_shorts_info_panel", BOOLEAN, TRUE),
|
|
||||||
HIDE_SHORTS_SOUND_BUTTON("revanced_hide_shorts_sound_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHORTS_CHANNEL_BAR("revanced_hide_shorts_channel_bar", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHORTS_NAVIGATION_BAR("revanced_hide_shorts_navigation_bar", BOOLEAN, TRUE, true),
|
|
||||||
|
|
||||||
// Seekbar
|
|
||||||
@Deprecated ENABLE_OLD_SEEKBAR_THUMBNAILS("revanced_enable_old_seekbar_thumbnails", BOOLEAN, TRUE),
|
|
||||||
RESTORE_OLD_SEEKBAR_THUMBNAILS("revanced_restore_old_seekbar_thumbnails", BOOLEAN, TRUE),
|
|
||||||
HIDE_SEEKBAR("revanced_hide_seekbar", BOOLEAN, FALSE),
|
|
||||||
HIDE_SEEKBAR_THUMBNAIL("revanced_hide_seekbar_thumbnail", BOOLEAN, FALSE),
|
|
||||||
SEEKBAR_CUSTOM_COLOR("revanced_seekbar_custom_color", BOOLEAN, TRUE, true),
|
|
||||||
SEEKBAR_CUSTOM_COLOR_VALUE("revanced_seekbar_custom_color_value", STRING, "#FF0000", true, parents(SEEKBAR_CUSTOM_COLOR)),
|
|
||||||
|
|
||||||
// Action buttons
|
|
||||||
HIDE_LIKE_DISLIKE_BUTTON("revanced_hide_like_dislike_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_LIVE_CHAT_BUTTON("revanced_hide_live_chat_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHARE_BUTTON("revanced_hide_share_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_REPORT_BUTTON("revanced_hide_report_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_REMIX_BUTTON("revanced_hide_remix_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_DOWNLOAD_BUTTON("revanced_hide_download_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_THANKS_BUTTON("revanced_hide_thanks_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_CLIP_BUTTON("revanced_hide_clip_button", BOOLEAN, TRUE),
|
|
||||||
HIDE_PLAYLIST_BUTTON("revanced_hide_playlist_button", BOOLEAN, FALSE),
|
|
||||||
HIDE_SHOP_BUTTON("revanced_hide_shop_button", BOOLEAN, TRUE),
|
|
||||||
|
|
||||||
// Player flyout menu items
|
|
||||||
HIDE_CAPTIONS_MENU("revanced_hide_player_flyout_captions", BOOLEAN, FALSE),
|
|
||||||
HIDE_ADDITIONAL_SETTINGS_MENU("revanced_hide_player_flyout_additional_settings", BOOLEAN, FALSE),
|
|
||||||
HIDE_LOOP_VIDEO_MENU("revanced_hide_player_flyout_loop_video", BOOLEAN, FALSE),
|
|
||||||
HIDE_AMBIENT_MODE_MENU("revanced_hide_player_flyout_ambient_mode", BOOLEAN, FALSE),
|
|
||||||
HIDE_REPORT_MENU("revanced_hide_player_flyout_report", BOOLEAN, TRUE),
|
|
||||||
HIDE_HELP_MENU("revanced_hide_player_flyout_help", BOOLEAN, TRUE),
|
|
||||||
HIDE_SPEED_MENU("revanced_hide_player_flyout_speed", BOOLEAN, FALSE),
|
|
||||||
HIDE_MORE_INFO_MENU("revanced_hide_player_flyout_more_info", BOOLEAN, TRUE),
|
|
||||||
HIDE_AUDIO_TRACK_MENU("revanced_hide_player_flyout_audio_track", BOOLEAN, FALSE),
|
|
||||||
HIDE_WATCH_IN_VR_MENU("revanced_hide_player_flyout_watch_in_vr", BOOLEAN, TRUE),
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
AUTO_CAPTIONS("revanced_auto_captions", BOOLEAN, FALSE),
|
|
||||||
DISABLE_ZOOM_HAPTICS("revanced_disable_zoom_haptics", BOOLEAN, TRUE),
|
|
||||||
EXTERNAL_BROWSER("revanced_external_browser", BOOLEAN, TRUE, true),
|
|
||||||
AUTO_REPEAT("revanced_auto_repeat", BOOLEAN, FALSE),
|
|
||||||
SEEKBAR_TAPPING("revanced_seekbar_tapping", BOOLEAN, TRUE),
|
|
||||||
SLIDE_TO_SEEK("revanced_slide_to_seek", BOOLEAN, FALSE),
|
|
||||||
@Deprecated DISABLE_FINE_SCRUBBING_GESTURE("revanced_disable_fine_scrubbing_gesture", BOOLEAN, TRUE),
|
|
||||||
DISABLE_PRECISE_SEEKING_GESTURE("revanced_disable_precise_seeking_gesture", BOOLEAN, TRUE),
|
|
||||||
SPOOF_SIGNATURE("revanced_spoof_signature_verification_enabled", BOOLEAN, TRUE, true,
|
|
||||||
"revanced_spoof_signature_verification_enabled_user_dialog_message"),
|
|
||||||
SPOOF_SIGNATURE_IN_FEED("revanced_spoof_signature_in_feed_enabled", BOOLEAN, FALSE, false,
|
|
||||||
parents(SPOOF_SIGNATURE)),
|
|
||||||
SPOOF_STORYBOARD_RENDERER("revanced_spoof_storyboard", BOOLEAN, TRUE, true,
|
|
||||||
parents(SPOOF_SIGNATURE)),
|
|
||||||
|
|
||||||
SPOOF_DEVICE_DIMENSIONS("revanced_spoof_device_dimensions", BOOLEAN, FALSE, true),
|
|
||||||
BYPASS_URL_REDIRECTS("revanced_bypass_url_redirects", BOOLEAN, TRUE),
|
|
||||||
ANNOUNCEMENTS("revanced_announcements", BOOLEAN, TRUE),
|
|
||||||
ANNOUNCEMENT_CONSUMER("revanced_announcement_consumer", STRING, ""),
|
|
||||||
ANNOUNCEMENT_LAST_HASH("revanced_announcement_last_hash", STRING, ""),
|
|
||||||
REMOVE_TRACKING_QUERY_PARAMETER("revanced_remove_tracking_query_parameter", BOOLEAN, TRUE),
|
|
||||||
REMOVE_VIEWER_DISCRETION_DIALOG("revanced_remove_viewer_discretion_dialog", BOOLEAN, FALSE,
|
|
||||||
"revanced_remove_viewer_discretion_dialog_user_dialog_message"),
|
|
||||||
|
|
||||||
// Swipe controls
|
|
||||||
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, true,
|
|
||||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
|
||||||
SWIPE_MAGNITUDE_THRESHOLD("revanced_swipe_threshold", INTEGER, 30, true,
|
|
||||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
|
||||||
SWIPE_OVERLAY_BACKGROUND_ALPHA("revanced_swipe_overlay_background_alpha", INTEGER, 127, true,
|
|
||||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
|
||||||
SWIPE_OVERLAY_TEXT_SIZE("revanced_swipe_text_overlay_size", INTEGER, 22, true,
|
|
||||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
|
||||||
SWIPE_OVERLAY_TIMEOUT("revanced_swipe_overlay_timeout", LONG, 500L, true,
|
|
||||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
|
||||||
SWIPE_SAVE_AND_RESTORE_BRIGHTNESS("revanced_swipe_save_and_restore_brightness", BOOLEAN, TRUE, true,
|
|
||||||
parents(SWIPE_BRIGHTNESS, SWIPE_VOLUME)),
|
|
||||||
|
|
||||||
// Debugging
|
|
||||||
DEBUG("revanced_debug", BOOLEAN, FALSE),
|
|
||||||
DEBUG_STACKTRACE("revanced_debug_stacktrace", BOOLEAN, FALSE, parents(DEBUG)),
|
|
||||||
DEBUG_PROTOBUFFER("revanced_debug_protobuffer", BOOLEAN, FALSE, parents(DEBUG)),
|
|
||||||
DEBUG_TOAST_ON_ERROR("revanced_debug_toast_on_error", BOOLEAN, TRUE, "revanced_debug_toast_on_error_user_dialog_message"),
|
|
||||||
|
|
||||||
// ReturnYoutubeDislike
|
|
||||||
RYD_ENABLED("ryd_enabled", BOOLEAN, TRUE, RETURN_YOUTUBE_DISLIKE),
|
|
||||||
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
|
|
||||||
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) {
|
|
||||||
return parents;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public final String path;
|
|
||||||
@NonNull
|
|
||||||
public final Object defaultValue;
|
|
||||||
@NonNull
|
|
||||||
public final SharedPrefCategory sharedPref;
|
|
||||||
@NonNull
|
|
||||||
public final ReturnType returnType;
|
|
||||||
/**
|
|
||||||
* If the app should be rebooted, if this setting is changed
|
|
||||||
*/
|
|
||||||
public final boolean rebootApp;
|
|
||||||
/**
|
|
||||||
* Set of boolean parent settings.
|
|
||||||
* If any of the parents are enabled, then this setting is available to configure.
|
|
||||||
*
|
|
||||||
* For example: {@link #DEBUG_STACKTRACE} is non-functional and cannot be configured,
|
|
||||||
* unless it's parent {@link #DEBUG} is enabled.
|
|
||||||
*
|
|
||||||
* Declaration is not needed for items that do not appear in the ReVanced Settings UI.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private final SettingsEnum[] parents;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirmation message to display, if the user tries to change the setting from the default value.
|
|
||||||
* Can only be used for {@link ReturnType#BOOLEAN} setting types.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public final StringRef userDialogMessage;
|
|
||||||
|
|
||||||
// Must be volatile, as some settings are read/write from different threads.
|
|
||||||
// Of note, the object value is persistently stored using SharedPreferences (which is thread safe).
|
|
||||||
@NonNull
|
|
||||||
private volatile Object value;
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
this.path = Objects.requireNonNull(path);
|
|
||||||
this.returnType = Objects.requireNonNull(returnType);
|
|
||||||
this.value = this.defaultValue = Objects.requireNonNull(defaultValue);
|
|
||||||
this.sharedPref = Objects.requireNonNull(prefName);
|
|
||||||
this.rebootApp = rebootApp;
|
|
||||||
|
|
||||||
if (userDialogMessage == null) {
|
|
||||||
this.userDialogMessage = null;
|
|
||||||
} else {
|
|
||||||
if (returnType != ReturnType.BOOLEAN) {
|
|
||||||
throw new IllegalArgumentException("must be Boolean type: " + path);
|
|
||||||
}
|
|
||||||
this.userDialogMessage = new StringRef(userDialogMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.parents = parents;
|
|
||||||
if (parents != null) {
|
|
||||||
for (SettingsEnum parent : parents) {
|
|
||||||
if (parent.returnType != ReturnType.BOOLEAN) {
|
|
||||||
throw new IllegalArgumentException("parent must be Boolean type: " + parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return pathToSetting.get(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void loadAllSettings() {
|
|
||||||
for (SettingsEnum setting : values()) {
|
|
||||||
setting.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
// region Migration
|
|
||||||
|
|
||||||
migrateOldSettingToNew(HIDE_VIDEO_WATERMARK, HIDE_VIDEO_CHANNEL_WATERMARK);
|
|
||||||
migrateOldSettingToNew(DISABLE_FINE_SCRUBBING_GESTURE, DISABLE_PRECISE_SEEKING_GESTURE);
|
|
||||||
migrateOldSettingToNew(SHOW_OLD_VIDEO_QUALITY_MENU, RESTORE_OLD_VIDEO_QUALITY_MENU);
|
|
||||||
migrateOldSettingToNew(ENABLE_OLD_SEEKBAR_THUMBNAILS, RESTORE_OLD_SEEKBAR_THUMBNAILS);
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
migrateOldSettingToNew(DEPRECATED_SB_UUID_OLD_MIGRATION_SETTING, SB_PRIVATE_USER_ID);
|
|
||||||
|
|
||||||
// This migration may need to remain here for a while.
|
|
||||||
// Older online guides will still reference using commas,
|
|
||||||
// and this code will automatically convert anything the user enters to newline format,
|
|
||||||
// and also migrate any imported older settings that using commas.
|
|
||||||
String componentsToFilter = SettingsEnum.CUSTOM_FILTER_STRINGS.getString();
|
|
||||||
if (componentsToFilter.contains(",")) {
|
|
||||||
LogHelper.printInfo(() -> "Migrating custom filter strings to new line format");
|
|
||||||
SettingsEnum.CUSTOM_FILTER_STRINGS.saveValue(componentsToFilter.replace(",", "\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
|
|
||||||
*/
|
|
||||||
private static void migrateOldSettingToNew(SettingsEnum oldSetting, SettingsEnum newSetting) {
|
|
||||||
if (!oldSetting.isSetToDefault()) {
|
|
||||||
LogHelper.printInfo(() -> "Migrating old setting of '" + oldSetting.value
|
|
||||||
+ "' from: " + oldSetting + " into replacement setting: " + newSetting);
|
|
||||||
newSetting.saveValue(oldSetting.value);
|
|
||||||
oldSetting.resetToDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void load() {
|
|
||||||
switch (returnType) {
|
|
||||||
case BOOLEAN:
|
|
||||||
value = sharedPref.getBoolean(path, (boolean) defaultValue);
|
|
||||||
break;
|
|
||||||
case INTEGER:
|
|
||||||
value = sharedPref.getIntegerString(path, (Integer) defaultValue);
|
|
||||||
break;
|
|
||||||
case LONG:
|
|
||||||
value = sharedPref.getLongString(path, (Long) defaultValue);
|
|
||||||
break;
|
|
||||||
case FLOAT:
|
|
||||||
value = sharedPref.getFloatString(path, (Float) defaultValue);
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
value = sharedPref.getString(path, (String) defaultValue);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException(name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
Objects.requireNonNull(newValue);
|
|
||||||
switch (setting.returnType) {
|
|
||||||
case BOOLEAN:
|
|
||||||
setting.value = Boolean.valueOf(newValue);
|
|
||||||
break;
|
|
||||||
case INTEGER:
|
|
||||||
setting.value = Integer.valueOf(newValue);
|
|
||||||
break;
|
|
||||||
case LONG:
|
|
||||||
setting.value = Long.valueOf(newValue);
|
|
||||||
break;
|
|
||||||
case FLOAT:
|
|
||||||
setting.value = Float.valueOf(newValue);
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
setting.value = newValue;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
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) {
|
|
||||||
setting.returnType.validate(newValue);
|
|
||||||
setting.value = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the value, and persistently saves it.
|
|
||||||
*/
|
|
||||||
public void saveValue(@NonNull Object 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);
|
|
||||||
break;
|
|
||||||
case INTEGER:
|
|
||||||
sharedPref.saveIntegerString(path, (Integer) newValue);
|
|
||||||
break;
|
|
||||||
case LONG:
|
|
||||||
sharedPref.saveLongString(path, (Long) newValue);
|
|
||||||
break;
|
|
||||||
case FLOAT:
|
|
||||||
sharedPref.saveFloatString(path, (Float) newValue);
|
|
||||||
break;
|
|
||||||
case STRING:
|
|
||||||
sharedPref.saveString(path, (String) newValue);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException(name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Identical to calling {@link #saveValue(Object)} using {@link #defaultValue}.
|
|
||||||
*/
|
|
||||||
public void resetToDefault() {
|
|
||||||
saveValue(defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return if this setting can be configured and used.
|
|
||||||
* <p>
|
|
||||||
* Not to be confused with {@link #getBoolean()}
|
|
||||||
*/
|
|
||||||
public boolean isAvailable() {
|
|
||||||
if (parents == null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
for (SettingsEnum parent : parents) {
|
|
||||||
if (parent.getBoolean()) return true;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getInt() {
|
|
||||||
return (Integer) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLong() {
|
|
||||||
return (Long) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getFloat() {
|
|
||||||
return (Float) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public String getString() {
|
|
||||||
return (String) value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the value of this setting as as generic object type.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public Object getObjectValue() {
|
|
||||||
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 ANNOUNCEMENT_CONSUMER: // Not useful to export, no reason to include it.
|
|
||||||
case SB_LAST_VIP_CHECK:
|
|
||||||
case SB_HIDE_EXPORT_WARNING:
|
|
||||||
case SB_SEEN_GUIDELINES:
|
|
||||||
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.resetToDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,196 +0,0 @@
|
|||||||
package app.revanced.integrations.settingsmenu;
|
|
||||||
|
|
||||||
import static app.revanced.integrations.utils.StringRef.str;
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.EditTextPreference;
|
|
||||||
import android.preference.ListPreference;
|
|
||||||
import android.preference.Preference;
|
|
||||||
import android.preference.PreferenceFragment;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.preference.SwitchPreference;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import app.revanced.integrations.patches.playback.speed.CustomPlaybackSpeedPatch;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
import app.revanced.integrations.settings.SharedPrefCategory;
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
import app.revanced.shared.settings.SettingsUtils;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
static void showRestartDialog(@NonNull Context contxt) {
|
|
||||||
String positiveButton = str("in_app_update_restart_button");
|
|
||||||
new AlertDialog.Builder(contxt).setMessage(str("pref_refresh_config"))
|
|
||||||
.setPositiveButton(positiveButton, (dialog, id) -> {
|
|
||||||
SettingsUtils.restartApp(contxt);
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.setCancelable(false)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
|
|
||||||
*/
|
|
||||||
private boolean showingUserDialogMessage;
|
|
||||||
|
|
||||||
SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
|
||||||
try {
|
|
||||||
SettingsEnum setting = SettingsEnum.settingFromPath(str);
|
|
||||||
if (setting == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
if (settingImportInProgress) {
|
|
||||||
switchPref.setChecked(setting.getBoolean());
|
|
||||||
} else {
|
|
||||||
SettingsEnum.setValue(setting, switchPref.isChecked());
|
|
||||||
}
|
|
||||||
} else if (pref instanceof EditTextPreference) {
|
|
||||||
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;
|
|
||||||
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(getContext(), (SwitchPreference) pref, setting);
|
|
||||||
} else if (setting.rebootApp) {
|
|
||||||
showRestartDialog(getContext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LogHelper.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@SuppressLint("ResourceType")
|
|
||||||
@Override
|
|
||||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onActivityCreated(savedInstanceState);
|
|
||||||
try {
|
|
||||||
PreferenceManager preferenceManager = getPreferenceManager();
|
|
||||||
preferenceManager.setSharedPreferencesName(SharedPrefCategory.YOUTUBE.prefName);
|
|
||||||
addPreferencesFromResource(ReVancedUtils.getResourceIdentifier("revanced_prefs", "xml"));
|
|
||||||
|
|
||||||
enableDisablePreferences();
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
CustomPlaybackSpeedPatch.initializeListPreference((ListPreference) defaultSpeedPreference);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set current value from SettingsEnum
|
|
||||||
for (SettingsEnum setting : SettingsEnum.values()) {
|
|
||||||
Preference preference = findPreference(setting.path);
|
|
||||||
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() failure", ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override // android.preference.PreferenceFragment, android.app.Fragment
|
|
||||||
public void onDestroy() {
|
|
||||||
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void enableDisablePreferences() {
|
|
||||||
for (SettingsEnum setting : SettingsEnum.values()) {
|
|
||||||
Preference preference = this.findPreference(setting.path);
|
|
||||||
if (preference != null) {
|
|
||||||
preference.setEnabled(setting.isAvailable());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets summary text to the currently selected list option.
|
|
||||||
*/
|
|
||||||
private void updateListPreferenceSummary(ListPreference listPreference, SettingsEnum setting) {
|
|
||||||
String objectStringValue = setting.getObjectValue().toString();
|
|
||||||
final int entryIndex = listPreference.findIndexOfValue(objectStringValue);
|
|
||||||
if (entryIndex >= 0) {
|
|
||||||
listPreference.setSummary(listPreference.getEntries()[entryIndex]);
|
|
||||||
listPreference.setValue(objectStringValue);
|
|
||||||
} else {
|
|
||||||
// Value is not an available option.
|
|
||||||
// User manually edited import data, or options changed and current selection is no longer available.
|
|
||||||
// Still show the value in the summary so it's clear that something is selected.
|
|
||||||
listPreference.setSummary(objectStringValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showSettingUserDialogConfirmation(@NonNull Context context, SwitchPreference switchPref, SettingsEnum setting) {
|
|
||||||
showingUserDialogMessage = true;
|
|
||||||
new AlertDialog.Builder(context)
|
|
||||||
.setTitle(str("revanced_settings_confirm_user_dialog_title"))
|
|
||||||
.setMessage(setting.userDialogMessage.toString())
|
|
||||||
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
|
||||||
if (setting.rebootApp) {
|
|
||||||
showRestartDialog(context);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
|
||||||
Boolean defaultBooleanValue = (Boolean) setting.defaultValue;
|
|
||||||
SettingsEnum.setValue(setting, defaultBooleanValue);
|
|
||||||
switchPref.setChecked(defaultBooleanValue);
|
|
||||||
})
|
|
||||||
.setOnDismissListener(dialog -> {
|
|
||||||
showingUserDialogMessage = false;
|
|
||||||
})
|
|
||||||
.setCancelable(false)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,230 +0,0 @@
|
|||||||
package app.revanced.integrations.settingsmenu;
|
|
||||||
|
|
||||||
import static app.revanced.integrations.utils.StringRef.str;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.preference.Preference;
|
|
||||||
import android.preference.PreferenceCategory;
|
|
||||||
import android.preference.PreferenceFragment;
|
|
||||||
import android.preference.PreferenceScreen;
|
|
||||||
import android.preference.SwitchPreference;
|
|
||||||
|
|
||||||
import app.revanced.integrations.patches.ReturnYouTubeDislikePatch;
|
|
||||||
import app.revanced.integrations.returnyoutubedislike.ReturnYouTubeDislike;
|
|
||||||
import app.revanced.integrations.returnyoutubedislike.requests.ReturnYouTubeDislikeApi;
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
private SwitchPreference percentagePreference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If segmented like/dislike button uses smaller compact layout.
|
|
||||||
*/
|
|
||||||
private SwitchPreference compactLayoutPreference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If segmented like/dislike button uses smaller compact layout.
|
|
||||||
*/
|
|
||||||
private SwitchPreference toastOnRYDNotAvailable;
|
|
||||||
|
|
||||||
private void updateUIState() {
|
|
||||||
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
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getPreferenceManager().setSharedPreferencesName(SharedPrefCategory.RETURN_YOUTUBE_DISLIKE.prefName);
|
|
||||||
|
|
||||||
Activity context = this.getActivity();
|
|
||||||
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
|
|
||||||
setPreferenceScreen(preferenceScreen);
|
|
||||||
|
|
||||||
SwitchPreference enabledPreference = new SwitchPreference(context);
|
|
||||||
enabledPreference.setChecked(SettingsEnum.RYD_ENABLED.getBoolean());
|
|
||||||
enabledPreference.setTitle(str("revanced_ryd_enable_title"));
|
|
||||||
enabledPreference.setSummaryOn(str("revanced_ryd_enable_summary_on"));
|
|
||||||
enabledPreference.setSummaryOff(str("revanced_ryd_enable_summary_off"));
|
|
||||||
enabledPreference.setOnPreferenceChangeListener((pref, newValue) -> {
|
|
||||||
final boolean rydIsEnabled = (Boolean) newValue;
|
|
||||||
SettingsEnum.RYD_ENABLED.saveValue(rydIsEnabled);
|
|
||||||
ReturnYouTubeDislikePatch.onRYDStatusChange(rydIsEnabled);
|
|
||||||
|
|
||||||
updateUIState();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
preferenceScreen.addPreference(enabledPreference);
|
|
||||||
|
|
||||||
shortsPreference = new SwitchPreference(context);
|
|
||||||
shortsPreference.setChecked(SettingsEnum.RYD_SHORTS.getBoolean());
|
|
||||||
shortsPreference.setTitle(str("revanced_ryd_shorts_title"));
|
|
||||||
String shortsSummary = str("revanced_ryd_shorts_summary_on",
|
|
||||||
ReturnYouTubeDislikePatch.IS_SPOOFING_TO_NON_LITHO_SHORTS_PLAYER
|
|
||||||
? ""
|
|
||||||
: "\n\n" + str("revanced_ryd_shorts_summary_disclaimer"));
|
|
||||||
shortsPreference.setSummaryOn(shortsSummary);
|
|
||||||
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_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_DISLIKE_PERCENTAGE.saveValue(newValue);
|
|
||||||
ReturnYouTubeDislike.clearAllUICaches();
|
|
||||||
updateUIState();
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
preferenceScreen.addPreference(percentagePreference);
|
|
||||||
|
|
||||||
compactLayoutPreference = new SwitchPreference(context);
|
|
||||||
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_COMPACT_LAYOUT.saveValue(newValue);
|
|
||||||
ReturnYouTubeDislike.clearAllUICaches();
|
|
||||||
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();
|
|
||||||
|
|
||||||
|
|
||||||
// About category
|
|
||||||
|
|
||||||
PreferenceCategory aboutCategory = new PreferenceCategory(context);
|
|
||||||
aboutCategory.setTitle(str("revanced_ryd_about"));
|
|
||||||
preferenceScreen.addPreference(aboutCategory);
|
|
||||||
|
|
||||||
// ReturnYouTubeDislike Website
|
|
||||||
|
|
||||||
Preference aboutWebsitePreference = new Preference(context);
|
|
||||||
aboutWebsitePreference.setTitle(str("revanced_ryd_attribution_title"));
|
|
||||||
aboutWebsitePreference.setSummary(str("revanced_ryd_attribution_summary"));
|
|
||||||
aboutWebsitePreference.setOnPreferenceClickListener(pref -> {
|
|
||||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
|
||||||
i.setData(Uri.parse("https://returnyoutubedislike.com"));
|
|
||||||
pref.getContext().startActivity(i);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
preferenceScreen.addPreference(aboutWebsitePreference);
|
|
||||||
|
|
||||||
// RYD API connection statistics
|
|
||||||
|
|
||||||
if (SettingsEnum.DEBUG.getBoolean()) {
|
|
||||||
PreferenceCategory emptyCategory = new PreferenceCategory(context); // vertical padding
|
|
||||||
preferenceScreen.addPreference(emptyCategory);
|
|
||||||
|
|
||||||
PreferenceCategory statisticsCategory = new PreferenceCategory(context);
|
|
||||||
statisticsCategory.setTitle(str("revanced_ryd_statistics_category_title"));
|
|
||||||
preferenceScreen.addPreference(statisticsCategory);
|
|
||||||
|
|
||||||
Preference statisticPreference;
|
|
||||||
|
|
||||||
statisticPreference = new Preference(context);
|
|
||||||
statisticPreference.setSelectable(false);
|
|
||||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeAverage_title"));
|
|
||||||
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeAverage()));
|
|
||||||
preferenceScreen.addPreference(statisticPreference);
|
|
||||||
|
|
||||||
statisticPreference = new Preference(context);
|
|
||||||
statisticPreference.setSelectable(false);
|
|
||||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeMin_title"));
|
|
||||||
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMin()));
|
|
||||||
preferenceScreen.addPreference(statisticPreference);
|
|
||||||
|
|
||||||
statisticPreference = new Preference(context);
|
|
||||||
statisticPreference.setSelectable(false);
|
|
||||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeMax_title"));
|
|
||||||
statisticPreference.setSummary(createMillisecondStringFromNumber(ReturnYouTubeDislikeApi.getFetchCallResponseTimeMax()));
|
|
||||||
preferenceScreen.addPreference(statisticPreference);
|
|
||||||
|
|
||||||
String fetchCallTimeWaitingLastSummary;
|
|
||||||
final long fetchCallTimeWaitingLast = ReturnYouTubeDislikeApi.getFetchCallResponseTimeLast();
|
|
||||||
if (fetchCallTimeWaitingLast == ReturnYouTubeDislikeApi.FETCH_CALL_RESPONSE_TIME_VALUE_RATE_LIMIT) {
|
|
||||||
fetchCallTimeWaitingLastSummary = str("revanced_ryd_statistics_getFetchCallResponseTimeLast_rate_limit_summary");
|
|
||||||
} else {
|
|
||||||
fetchCallTimeWaitingLastSummary = createMillisecondStringFromNumber(fetchCallTimeWaitingLast);
|
|
||||||
}
|
|
||||||
statisticPreference = new Preference(context);
|
|
||||||
statisticPreference.setSelectable(false);
|
|
||||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallResponseTimeLast_title"));
|
|
||||||
statisticPreference.setSummary(fetchCallTimeWaitingLastSummary);
|
|
||||||
preferenceScreen.addPreference(statisticPreference);
|
|
||||||
|
|
||||||
statisticPreference = new Preference(context);
|
|
||||||
statisticPreference.setSelectable(false);
|
|
||||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallCount_title"));
|
|
||||||
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getFetchCallCount(),
|
|
||||||
"revanced_ryd_statistics_getFetchCallCount_zero_summary",
|
|
||||||
"revanced_ryd_statistics_getFetchCallCount_non_zero_summary"));
|
|
||||||
preferenceScreen.addPreference(statisticPreference);
|
|
||||||
|
|
||||||
statisticPreference = new Preference(context);
|
|
||||||
statisticPreference.setSelectable(false);
|
|
||||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getFetchCallNumberOfFailures_title"));
|
|
||||||
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getFetchCallNumberOfFailures(),
|
|
||||||
"revanced_ryd_statistics_getFetchCallNumberOfFailures_zero_summary",
|
|
||||||
"revanced_ryd_statistics_getFetchCallNumberOfFailures_non_zero_summary"));
|
|
||||||
preferenceScreen.addPreference(statisticPreference);
|
|
||||||
|
|
||||||
statisticPreference = new Preference(context);
|
|
||||||
statisticPreference.setSelectable(false);
|
|
||||||
statisticPreference.setTitle(str("revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_title"));
|
|
||||||
statisticPreference.setSummary(createSummaryText(ReturnYouTubeDislikeApi.getNumberOfRateLimitRequestsEncountered(),
|
|
||||||
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_zero_summary",
|
|
||||||
"revanced_ryd_statistics_getNumberOfRateLimitRequestsEncountered_non_zero_summary"));
|
|
||||||
preferenceScreen.addPreference(statisticPreference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String createSummaryText(int value, String summaryStringZeroKey, String summaryStringOneOrMoreKey) {
|
|
||||||
if (value == 0) {
|
|
||||||
return str(summaryStringZeroKey);
|
|
||||||
}
|
|
||||||
return String.format(str(summaryStringOneOrMoreKey), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String createMillisecondStringFromNumber(long number) {
|
|
||||||
return String.format(str("revanced_ryd_statistics_millisecond_text"), number);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,16 +1,19 @@
|
|||||||
package app.revanced.integrations.utils;
|
package app.revanced.integrations.shared;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG;
|
||||||
|
import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_STACKTRACE;
|
||||||
|
import static app.revanced.integrations.shared.settings.BaseSettings.DEBUG_TOAST_ON_ERROR;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
public class Logger {
|
||||||
|
|
||||||
public class LogHelper {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log messages using lambdas.
|
* Log messages using lambdas.
|
||||||
@ -52,13 +55,13 @@ public class LogHelper {
|
|||||||
/**
|
/**
|
||||||
* Logs debug messages under the outer class name of the code calling this method.
|
* Logs debug messages under the outer class name of the code calling this method.
|
||||||
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()}
|
* Whenever possible, the log string should be constructed entirely inside {@link LogMessage#buildMessageString()}
|
||||||
* so the performance cost of building strings is paid only if {@link SettingsEnum#DEBUG} is enabled.
|
* so the performance cost of building strings is paid only if {@link BaseSettings#DEBUG} is enabled.
|
||||||
*/
|
*/
|
||||||
public static void printDebug(@NonNull LogMessage message) {
|
public static void printDebug(@NonNull LogMessage message) {
|
||||||
if (SettingsEnum.DEBUG.getBoolean()) {
|
if (DEBUG.get()) {
|
||||||
var messageString = message.buildMessageString();
|
var messageString = message.buildMessageString();
|
||||||
|
|
||||||
if (SettingsEnum.DEBUG_STACKTRACE.getBoolean()) {
|
if (DEBUG_STACKTRACE.get()) {
|
||||||
var builder = new StringBuilder(messageString);
|
var builder = new StringBuilder(messageString);
|
||||||
var sw = new StringWriter();
|
var sw = new StringWriter();
|
||||||
new Throwable().printStackTrace(new PrintWriter(sw));
|
new Throwable().printStackTrace(new PrintWriter(sw));
|
||||||
@ -125,12 +128,21 @@ public class LogHelper {
|
|||||||
} else {
|
} else {
|
||||||
Log.e(logMessage, messageString, ex);
|
Log.e(logMessage, messageString, ex);
|
||||||
}
|
}
|
||||||
if (SettingsEnum.DEBUG_TOAST_ON_ERROR.getBoolean()) {
|
if (DEBUG_TOAST_ON_ERROR.get()) {
|
||||||
String toastMessageToDisplay = (userToastMessage != null)
|
String toastMessageToDisplay = (userToastMessage != null)
|
||||||
? userToastMessage
|
? userToastMessage
|
||||||
: outerClassSimpleName + ": " + messageString;
|
: outerClassSimpleName + ": " + messageString;
|
||||||
ReVancedUtils.showToastLong(toastMessageToDisplay);
|
Utils.showToastLong(toastMessageToDisplay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#context} may not be initialized.
|
||||||
|
* Always logs even if Debugging is not enabled.
|
||||||
|
* Normally this method should not be used.
|
||||||
|
*/
|
||||||
|
public static void initializationError(@NonNull Class<?> callingClass, @NonNull String message, @Nullable Exception ex) {
|
||||||
|
Log.e(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message, ex);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.integrations.utils;
|
package app.revanced.integrations.shared;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@ -102,7 +102,7 @@ public class StringRef {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
if (resources == null || packageName == null) {
|
if (resources == null || packageName == null) {
|
||||||
Context context = ReVancedUtils.getContext();
|
Context context = Utils.getContext();
|
||||||
resources = context.getResources();
|
resources = context.getResources();
|
||||||
packageName = context.getPackageName();
|
packageName = context.getPackageName();
|
||||||
}
|
}
|
||||||
@ -110,11 +110,11 @@ public class StringRef {
|
|||||||
if (resources != null) {
|
if (resources != null) {
|
||||||
final int identifier = resources.getIdentifier(value, "string", packageName);
|
final int identifier = resources.getIdentifier(value, "string", packageName);
|
||||||
if (identifier == 0)
|
if (identifier == 0)
|
||||||
LogHelper.printException(() -> "Resource not found: " + value);
|
Logger.printException(() -> "Resource not found: " + value);
|
||||||
else
|
else
|
||||||
value = resources.getString(identifier);
|
value = resources.getString(identifier);
|
||||||
} else {
|
} else {
|
||||||
LogHelper.printException(() -> "Could not resolve resources!");
|
Logger.printException(() -> "Could not resolve resources!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
@ -1,7 +1,8 @@
|
|||||||
package app.revanced.integrations.utils;
|
package app.revanced.integrations.shared;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@ -13,24 +14,34 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.AnimationUtils;
|
import android.view.animation.AnimationUtils;
|
||||||
import android.widget.*;
|
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.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
|
||||||
|
|
||||||
import java.text.Bidi;
|
import java.text.Bidi;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.SynchronousQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class ReVancedUtils {
|
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public static Context context;
|
public static Context context;
|
||||||
|
|
||||||
private static String versionName;
|
private static String versionName;
|
||||||
|
|
||||||
private ReVancedUtils() {
|
private Utils() {
|
||||||
} // utility class
|
} // utility class
|
||||||
|
|
||||||
public static String getVersionName() {
|
public static String getVersionName() {
|
||||||
@ -51,7 +62,7 @@ public class ReVancedUtils {
|
|||||||
0
|
0
|
||||||
);
|
);
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
LogHelper.printException(() -> "Failed to get package info", e);
|
Logger.printException(() -> "Failed to get package info", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,10 +75,10 @@ public class ReVancedUtils {
|
|||||||
* @param condition The setting to check for hiding the view.
|
* @param condition The setting to check for hiding the view.
|
||||||
* @param view The view to hide.
|
* @param view The view to hide.
|
||||||
*/
|
*/
|
||||||
public static void hideViewBy1dpUnderCondition(SettingsEnum condition, View view) {
|
public static void hideViewBy1dpUnderCondition(BooleanSetting condition, View view) {
|
||||||
if (!condition.getBoolean()) return;
|
if (!condition.get()) return;
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
Logger.printDebug(() -> "Hiding view with setting: " + condition);
|
||||||
|
|
||||||
hideViewByLayoutParams(view);
|
hideViewByLayoutParams(view);
|
||||||
}
|
}
|
||||||
@ -78,10 +89,10 @@ public class ReVancedUtils {
|
|||||||
* @param condition The setting to check for hiding the view.
|
* @param condition The setting to check for hiding the view.
|
||||||
* @param view The view to hide.
|
* @param view The view to hide.
|
||||||
*/
|
*/
|
||||||
public static void hideViewUnderCondition(SettingsEnum condition, View view) {
|
public static void hideViewUnderCondition(BooleanSetting condition, View view) {
|
||||||
if (!condition.getBoolean()) return;
|
if (!condition.get()) return;
|
||||||
|
|
||||||
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
Logger.printDebug(() -> "Hiding view with setting: " + condition);
|
||||||
|
|
||||||
view.setVisibility(View.GONE);
|
view.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@ -119,7 +130,7 @@ public class ReVancedUtils {
|
|||||||
@SuppressWarnings("UnusedReturnValue")
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
public static long doNothingForDuration(long amountOfTimeToWaste) {
|
public static long doNothingForDuration(long amountOfTimeToWaste) {
|
||||||
final long timeCalculationStarted = System.currentTimeMillis();
|
final long timeCalculationStarted = System.currentTimeMillis();
|
||||||
LogHelper.printDebug(() -> "Artificially creating delay of: " + amountOfTimeToWaste + "ms");
|
Logger.printDebug(() -> "Artificially creating delay of: " + amountOfTimeToWaste + "ms");
|
||||||
|
|
||||||
long meaninglessValue = 0;
|
long meaninglessValue = 0;
|
||||||
while (System.currentTimeMillis() - timeCalculationStarted < amountOfTimeToWaste) {
|
while (System.currentTimeMillis() - timeCalculationStarted < amountOfTimeToWaste) {
|
||||||
@ -196,16 +207,26 @@ public class ReVancedUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void restartApp(@NonNull Context context) {
|
||||||
|
String packageName = context.getPackageName();
|
||||||
|
Intent intent = context.getPackageManager().getLaunchIntentForPackage(packageName);
|
||||||
|
Intent mainIntent = Intent.makeRestartActivityTask(intent.getComponent());
|
||||||
|
// Required for API 34 and later
|
||||||
|
// Ref: https://developer.android.com/about/versions/14/behavior-changes-14#safer-intents
|
||||||
|
mainIntent.setPackage(packageName);
|
||||||
|
context.startActivity(mainIntent);
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
public interface MatchFilter<T> {
|
public interface MatchFilter<T> {
|
||||||
boolean matches(T object);
|
boolean matches(T object);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Context getContext() {
|
public static Context getContext() {
|
||||||
if (context != null) {
|
if (context == null) {
|
||||||
return context;
|
Logger.initializationError(Utils.class, "Context is null, returning null!", null);
|
||||||
}
|
}
|
||||||
LogHelper.printException(() -> "Context is null, returning null!");
|
return context;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setClipboard(@NonNull String text) {
|
public static void setClipboard(@NonNull String text) {
|
||||||
@ -249,11 +270,10 @@ public class ReVancedUtils {
|
|||||||
private static void showToast(@NonNull String messageToToast, int toastDuration) {
|
private static void showToast(@NonNull String messageToToast, int toastDuration) {
|
||||||
Objects.requireNonNull(messageToToast);
|
Objects.requireNonNull(messageToToast);
|
||||||
runOnMainThreadNowOrLater(() -> {
|
runOnMainThreadNowOrLater(() -> {
|
||||||
// cannot use getContext(), otherwise if context is null it will cause infinite recursion of error logging
|
|
||||||
if (context == null) {
|
if (context == null) {
|
||||||
LogHelper.printDebug(() -> "Cannot show toast (context is null)");
|
Logger.initializationError(Utils.class, "Cannot show toast (context is null): " + messageToToast, null);
|
||||||
} else {
|
} else {
|
||||||
LogHelper.printDebug(() -> "Showing toast: " + messageToToast);
|
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
||||||
Toast.makeText(context, messageToToast, toastDuration).show();
|
Toast.makeText(context, messageToToast, toastDuration).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,7 +297,7 @@ public class ReVancedUtils {
|
|||||||
try {
|
try {
|
||||||
runnable.run();
|
runnable.run();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> runnable.getClass() + ": " + ex.getMessage(), ex);
|
Logger.printException(() -> runnable.getClass() + ": " + ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
new Handler(Looper.getMainLooper()).postDelayed(loggingRunnable, delayMillis);
|
new Handler(Looper.getMainLooper()).postDelayed(loggingRunnable, delayMillis);
|
||||||
@ -364,7 +384,7 @@ public class ReVancedUtils {
|
|||||||
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(1, 1);
|
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(1, 1);
|
||||||
view.setLayoutParams(layoutParams5);
|
view.setLayoutParams(layoutParams5);
|
||||||
} else {
|
} else {
|
||||||
LogHelper.printDebug(() -> "Hidden view with id " + view.getId());
|
Logger.printDebug(() -> "Hidden view with id " + view.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,4 +393,4 @@ public class ReVancedUtils {
|
|||||||
MOBILE,
|
MOBILE,
|
||||||
OTHER,
|
OTHER,
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package app.revanced.integrations.shared.settings;
|
||||||
|
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
import static app.revanced.integrations.shared.settings.Setting.parent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Settings shared across multiple apps.
|
||||||
|
*
|
||||||
|
* To ensure this class is loaded when the UI is created, app specific setting bundles should extend
|
||||||
|
* or reference this class.
|
||||||
|
*/
|
||||||
|
public class BaseSettings {
|
||||||
|
public static final BooleanSetting DEBUG = new BooleanSetting("revanced_debug", FALSE);
|
||||||
|
public static final BooleanSetting DEBUG_STACKTRACE = new BooleanSetting("revanced_debug_stacktrace", FALSE, parent(DEBUG));
|
||||||
|
public static final BooleanSetting DEBUG_TOAST_ON_ERROR = new BooleanSetting("revanced_debug_toast_on_error", TRUE, "revanced_debug_toast_on_error_user_dialog_message");
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package app.revanced.integrations.shared.settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class BooleanSetting extends Setting<Boolean> {
|
||||||
|
public BooleanSetting(String key, Boolean defaultValue) {
|
||||||
|
super(key, defaultValue);
|
||||||
|
}
|
||||||
|
public BooleanSetting(String key, Boolean defaultValue, boolean rebootApp) {
|
||||||
|
super(key, defaultValue, rebootApp);
|
||||||
|
}
|
||||||
|
public BooleanSetting(String key, Boolean defaultValue, boolean rebootApp, boolean includeWithImportExport) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport);
|
||||||
|
}
|
||||||
|
public BooleanSetting(String key, Boolean defaultValue, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, userDialogMessage);
|
||||||
|
}
|
||||||
|
public BooleanSetting(String key, Boolean defaultValue, Availability availability) {
|
||||||
|
super(key, defaultValue, availability);
|
||||||
|
}
|
||||||
|
public BooleanSetting(String key, Boolean defaultValue, boolean rebootApp, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage);
|
||||||
|
}
|
||||||
|
public BooleanSetting(String key, Boolean defaultValue, boolean rebootApp, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, availability);
|
||||||
|
}
|
||||||
|
public BooleanSetting(String key, Boolean defaultValue, boolean rebootApp, String userDialogMessage, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
public BooleanSetting(@NonNull String key, @NonNull Boolean defaultValue, boolean rebootApp, boolean includeWithImportExport, @Nullable String userDialogMessage, @Nullable Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets, but does _not_ persistently save the value.
|
||||||
|
* This method is only to be used by the Settings preference code.
|
||||||
|
*
|
||||||
|
* This intentionally is a static method to deter
|
||||||
|
* accidental usage when {@link #save(Boolean)} was intnded.
|
||||||
|
*/
|
||||||
|
public static void privateSetValue(@NonNull BooleanSetting setting, @NonNull Boolean newValue) {
|
||||||
|
setting.value = Objects.requireNonNull(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void load() {
|
||||||
|
value = preferences.getBoolean(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean readFromJSON(JSONObject json, String importExportKey) throws JSONException {
|
||||||
|
return json.getBoolean(importExportKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setValueFromString(@NonNull String newValue) {
|
||||||
|
value = Boolean.valueOf(Objects.requireNonNull(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(@NonNull Boolean newValue) {
|
||||||
|
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
||||||
|
value = Objects.requireNonNull(newValue);
|
||||||
|
preferences.saveBoolean(key, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Boolean get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package app.revanced.integrations.shared.settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class FloatSetting extends Setting<Float> {
|
||||||
|
|
||||||
|
public FloatSetting(String key, Float defaultValue) {
|
||||||
|
super(key, defaultValue);
|
||||||
|
}
|
||||||
|
public FloatSetting(String key, Float defaultValue, boolean rebootApp) {
|
||||||
|
super(key, defaultValue, rebootApp);
|
||||||
|
}
|
||||||
|
public FloatSetting(String key, Float defaultValue, boolean rebootApp, boolean includeWithImportExport) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport);
|
||||||
|
}
|
||||||
|
public FloatSetting(String key, Float defaultValue, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, userDialogMessage);
|
||||||
|
}
|
||||||
|
public FloatSetting(String key, Float defaultValue, Availability availability) {
|
||||||
|
super(key, defaultValue, availability);
|
||||||
|
}
|
||||||
|
public FloatSetting(String key, Float defaultValue, boolean rebootApp, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage);
|
||||||
|
}
|
||||||
|
public FloatSetting(String key, Float defaultValue, boolean rebootApp, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, availability);
|
||||||
|
}
|
||||||
|
public FloatSetting(String key, Float defaultValue, boolean rebootApp, String userDialogMessage, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
public FloatSetting(@NonNull String key, @NonNull Float defaultValue, boolean rebootApp, boolean includeWithImportExport, @Nullable String userDialogMessage, @Nullable Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void load() {
|
||||||
|
value = preferences.getFloatString(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Float readFromJSON(JSONObject json, String importExportKey) throws JSONException {
|
||||||
|
return (float) json.getDouble(importExportKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setValueFromString(@NonNull String newValue) {
|
||||||
|
value = Float.valueOf(Objects.requireNonNull(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(@NonNull Float newValue) {
|
||||||
|
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
||||||
|
value = Objects.requireNonNull(newValue);
|
||||||
|
preferences.saveFloatString(key, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Float get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package app.revanced.integrations.shared.settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class IntegerSetting extends Setting<Integer> {
|
||||||
|
|
||||||
|
public IntegerSetting(String key, Integer defaultValue) {
|
||||||
|
super(key, defaultValue);
|
||||||
|
}
|
||||||
|
public IntegerSetting(String key, Integer defaultValue, boolean rebootApp) {
|
||||||
|
super(key, defaultValue, rebootApp);
|
||||||
|
}
|
||||||
|
public IntegerSetting(String key, Integer defaultValue, boolean rebootApp, boolean includeWithImportExport) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport);
|
||||||
|
}
|
||||||
|
public IntegerSetting(String key, Integer defaultValue, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, userDialogMessage);
|
||||||
|
}
|
||||||
|
public IntegerSetting(String key, Integer defaultValue, Availability availability) {
|
||||||
|
super(key, defaultValue, availability);
|
||||||
|
}
|
||||||
|
public IntegerSetting(String key, Integer defaultValue, boolean rebootApp, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage);
|
||||||
|
}
|
||||||
|
public IntegerSetting(String key, Integer defaultValue, boolean rebootApp, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, availability);
|
||||||
|
}
|
||||||
|
public IntegerSetting(String key, Integer defaultValue, boolean rebootApp, String userDialogMessage, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
public IntegerSetting(@NonNull String key, @NonNull Integer defaultValue, boolean rebootApp, boolean includeWithImportExport, @Nullable String userDialogMessage, @Nullable Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void load() {
|
||||||
|
value = preferences.getIntegerString(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Integer readFromJSON(JSONObject json, String importExportKey) throws JSONException {
|
||||||
|
return json.getInt(importExportKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setValueFromString(@NonNull String newValue) {
|
||||||
|
value = Integer.valueOf(Objects.requireNonNull(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(@NonNull Integer newValue) {
|
||||||
|
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
||||||
|
value = Objects.requireNonNull(newValue);
|
||||||
|
preferences.saveIntegerString(key, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Integer get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package app.revanced.integrations.shared.settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class LongSetting extends Setting<Long> {
|
||||||
|
|
||||||
|
public LongSetting(String key, Long defaultValue) {
|
||||||
|
super(key, defaultValue);
|
||||||
|
}
|
||||||
|
public LongSetting(String key, Long defaultValue, boolean rebootApp) {
|
||||||
|
super(key, defaultValue, rebootApp);
|
||||||
|
}
|
||||||
|
public LongSetting(String key, Long defaultValue, boolean rebootApp, boolean includeWithImportExport) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport);
|
||||||
|
}
|
||||||
|
public LongSetting(String key, Long defaultValue, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, userDialogMessage);
|
||||||
|
}
|
||||||
|
public LongSetting(String key, Long defaultValue, Availability availability) {
|
||||||
|
super(key, defaultValue, availability);
|
||||||
|
}
|
||||||
|
public LongSetting(String key, Long defaultValue, boolean rebootApp, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage);
|
||||||
|
}
|
||||||
|
public LongSetting(String key, Long defaultValue, boolean rebootApp, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, availability);
|
||||||
|
}
|
||||||
|
public LongSetting(String key, Long defaultValue, boolean rebootApp, String userDialogMessage, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
public LongSetting(@NonNull String key, @NonNull Long defaultValue, boolean rebootApp, boolean includeWithImportExport, @Nullable String userDialogMessage, @Nullable Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void load() {
|
||||||
|
value = preferences.getLongString(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Long readFromJSON(JSONObject json, String importExportKey) throws JSONException {
|
||||||
|
return json.getLong(importExportKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setValueFromString(@NonNull String newValue) {
|
||||||
|
value = Long.valueOf(Objects.requireNonNull(newValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(@NonNull Long newValue) {
|
||||||
|
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
||||||
|
value = Objects.requireNonNull(newValue);
|
||||||
|
preferences.saveLongString(key, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Long get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,429 @@
|
|||||||
|
package app.revanced.integrations.shared.settings;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.StringRef;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.shared.settings.preference.SharedPrefCategory;
|
||||||
|
import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract class Setting<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if a {@link Setting} is available to edit and use.
|
||||||
|
* Typically this is dependent upon other BooleanSetting(s) set to 'true',
|
||||||
|
* but this can be used to call into integrations code and check other conditions.
|
||||||
|
*/
|
||||||
|
public interface Availability {
|
||||||
|
boolean isAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Availability based on a single parent setting being enabled.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Availability parent(@NonNull BooleanSetting parent) {
|
||||||
|
return parent::get;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Availability based on all parents being enabled.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Availability parentsAll(@NonNull BooleanSetting... parents) {
|
||||||
|
return () -> {
|
||||||
|
for (BooleanSetting parent : parents) {
|
||||||
|
if (!parent.get()) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Availability based on any parent being enabled.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static Availability parentsAny(@NonNull BooleanSetting... parents) {
|
||||||
|
return () -> {
|
||||||
|
for (BooleanSetting parent : parents) {
|
||||||
|
if (parent.get()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All settings that were instantiated.
|
||||||
|
* When a new setting is created, it is automatically added to this list.
|
||||||
|
*/
|
||||||
|
private static final List<Setting<?>> SETTINGS = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map of setting path to setting object.
|
||||||
|
*/
|
||||||
|
private static final Map<String, Setting<?>> PATH_TO_SETTINGS = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference all instances are saved to.
|
||||||
|
*/
|
||||||
|
public static final SharedPrefCategory preferences = new SharedPrefCategory("revanced_prefs");
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Setting<?> getSettingFromPath(@NonNull String str) {
|
||||||
|
return PATH_TO_SETTINGS.get(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return All settings that have been created.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static List<Setting<?>> allLoadedSettings() {
|
||||||
|
return Collections.unmodifiableList(SETTINGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return All settings that have been created, sorted by keys.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
private static List<Setting<?>> allLoadedSettingsSorted() {
|
||||||
|
Collections.sort(SETTINGS, (Setting<?> o1, Setting<?> o2) -> o1.key.compareTo(o2.key));
|
||||||
|
return Collections.unmodifiableList(SETTINGS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key used to store the value in the shared preferences.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public final String key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default value of the setting.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public final T defaultValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the app should be rebooted, if this setting is changed
|
||||||
|
*/
|
||||||
|
public final boolean rebootApp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this setting should be included when importing/exporting settings.
|
||||||
|
*/
|
||||||
|
public final boolean includeWithImportExport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this setting is available to edit and use.
|
||||||
|
* Not to be confused with it's status returned from {@link #get()}.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private final Availability availability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirmation message to display, if the user tries to change the setting from the default value.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public final StringRef userDialogMessage;
|
||||||
|
|
||||||
|
// Must be volatile, as some settings are read/write from different threads.
|
||||||
|
// Of note, the object value is persistently stored using SharedPreferences (which is thread safe).
|
||||||
|
/**
|
||||||
|
* The value of the setting.
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
protected volatile T value;
|
||||||
|
|
||||||
|
public Setting(String key, T defaultValue) {
|
||||||
|
this(key, defaultValue, false, true, null, null);
|
||||||
|
}
|
||||||
|
public Setting(String key, T defaultValue, boolean rebootApp) {
|
||||||
|
this(key, defaultValue, rebootApp, true, null, null);
|
||||||
|
}
|
||||||
|
public Setting(String key, T defaultValue, boolean rebootApp, boolean includeWithImportExport) {
|
||||||
|
this(key, defaultValue, rebootApp, includeWithImportExport, null, null);
|
||||||
|
}
|
||||||
|
public Setting(String key, T defaultValue, String userDialogMessage) {
|
||||||
|
this(key, defaultValue, false, true, userDialogMessage, null);
|
||||||
|
}
|
||||||
|
public Setting(String key, T defaultValue, Availability availability) {
|
||||||
|
this(key, defaultValue, false, true, null, availability);
|
||||||
|
}
|
||||||
|
public Setting(String key, T defaultValue, boolean rebootApp, String userDialogMessage) {
|
||||||
|
this(key, defaultValue, rebootApp, true, userDialogMessage, null);
|
||||||
|
}
|
||||||
|
public Setting(String key, T defaultValue, boolean rebootApp, Availability availability) {
|
||||||
|
this(key, defaultValue, rebootApp, true, null, availability);
|
||||||
|
}
|
||||||
|
public Setting(String key, T defaultValue, boolean rebootApp, String userDialogMessage, Availability availability) {
|
||||||
|
this(key, defaultValue, rebootApp, true, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A setting backed by a shared preference.
|
||||||
|
*
|
||||||
|
* @param key The key used to store the value in the shared preferences.
|
||||||
|
* @param defaultValue The default value of the setting.
|
||||||
|
* @param rebootApp If the app should be rebooted, if this setting is changed.
|
||||||
|
* @param includeWithImportExport If this setting should be shown in the import/export dialog.
|
||||||
|
* @param userDialogMessage Confirmation message to display, if the user tries to change the setting from the default value.
|
||||||
|
* @param availability Condition that must be true, for this setting to be available to configure.
|
||||||
|
*/
|
||||||
|
public Setting(@NonNull String key,
|
||||||
|
@NonNull T defaultValue,
|
||||||
|
boolean rebootApp,
|
||||||
|
boolean includeWithImportExport,
|
||||||
|
@Nullable String userDialogMessage,
|
||||||
|
@Nullable Availability availability
|
||||||
|
) {
|
||||||
|
this.key = Objects.requireNonNull(key);
|
||||||
|
this.value = this.defaultValue = Objects.requireNonNull(defaultValue);
|
||||||
|
this.rebootApp = rebootApp;
|
||||||
|
this.includeWithImportExport = includeWithImportExport;
|
||||||
|
this.userDialogMessage = (userDialogMessage == null) ? null : new StringRef(userDialogMessage);
|
||||||
|
this.availability = availability;
|
||||||
|
|
||||||
|
SETTINGS.add(this);
|
||||||
|
if (PATH_TO_SETTINGS.put(key, this) != null) {
|
||||||
|
// Debug setting may not be created yet so using Logger may cause an initialization crash.
|
||||||
|
// Show a toast instead.
|
||||||
|
Utils.showToastLong(this.getClass().getSimpleName()
|
||||||
|
+ " error: Duplicate Setting key found: " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate a setting value if the path is renamed but otherwise the old and new settings are identical.
|
||||||
|
*/
|
||||||
|
public static void migrateOldSettingToNew(@NonNull Setting<?> oldSetting, @NonNull Setting newSetting) {
|
||||||
|
if (!oldSetting.isSetToDefault()) {
|
||||||
|
Logger.printInfo(() -> "Migrating old setting value: " + oldSetting + " into replacement setting: " + newSetting);
|
||||||
|
//noinspection unchecked
|
||||||
|
newSetting.save(oldSetting.value);
|
||||||
|
oldSetting.resetToDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate an old Setting value previously stored in a different SharedPreference.
|
||||||
|
*
|
||||||
|
* This method will be deleted in the future.
|
||||||
|
*/
|
||||||
|
public static void migrateFromOldPreferences(@NonNull SharedPrefCategory oldPrefs, @NonNull Setting setting, String settingKey) {
|
||||||
|
if (!oldPrefs.preferences.contains(settingKey)) {
|
||||||
|
return; // Nothing to do.
|
||||||
|
}
|
||||||
|
Object newValue = setting.get();
|
||||||
|
final Object migratedValue;
|
||||||
|
if (setting instanceof BooleanSetting) {
|
||||||
|
migratedValue = oldPrefs.getBoolean(settingKey, (Boolean) newValue);
|
||||||
|
} else if (setting instanceof IntegerSetting) {
|
||||||
|
migratedValue = oldPrefs.getIntegerString(settingKey, (Integer) newValue);
|
||||||
|
} else if (setting instanceof LongSetting) {
|
||||||
|
migratedValue = oldPrefs.getLongString(settingKey, (Long) newValue);
|
||||||
|
} else if (setting instanceof FloatSetting) {
|
||||||
|
migratedValue = oldPrefs.getFloatString(settingKey, (Float) newValue);
|
||||||
|
} else if (setting instanceof StringSetting) {
|
||||||
|
migratedValue = oldPrefs.getString(settingKey, (String) newValue);
|
||||||
|
} else {
|
||||||
|
Logger.printException(() -> "Unknown setting: " + setting);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
oldPrefs.preferences.edit().remove(settingKey).apply(); // Remove the old setting.
|
||||||
|
if (migratedValue.equals(newValue)) {
|
||||||
|
Logger.printDebug(() -> "Value does not need migrating: " + settingKey);
|
||||||
|
return; // Old value is already equal to the new setting value.
|
||||||
|
}
|
||||||
|
Logger.printDebug(() -> "Migrating old preference value into current preference: " + settingKey);
|
||||||
|
//noinspection unchecked
|
||||||
|
setting.save(migratedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets, but does _not_ persistently save the value.
|
||||||
|
* This method is only to be used by the Settings preference code.
|
||||||
|
*
|
||||||
|
* This intentionally is a static method to deter
|
||||||
|
* accidental usage when {@link #save(Object)} was intended.
|
||||||
|
*/
|
||||||
|
public static void privateSetValueFromString(@NonNull Setting<?> setting, @NonNull String newValue) {
|
||||||
|
setting.setValueFromString(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of {@link #value}, but do not save to {@link #preferences}.
|
||||||
|
*/
|
||||||
|
protected abstract void setValueFromString(@NonNull String newValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load and set the value of {@link #value}.
|
||||||
|
*/
|
||||||
|
protected abstract void load();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persistently saves the value.
|
||||||
|
*/
|
||||||
|
public abstract void save(@NonNull T newValue);
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public abstract T get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identical to calling {@link #save(Object)} using {@link #defaultValue}.
|
||||||
|
*/
|
||||||
|
public void resetToDefault() {
|
||||||
|
save(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if this setting can be configured and used.
|
||||||
|
*/
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return availability == null || availability.isAvailable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if the currently set value is the same as {@link #defaultValue}
|
||||||
|
*/
|
||||||
|
public boolean isSetToDefault() {
|
||||||
|
return value.equals(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return key + "=" + get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// region 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 (key.startsWith(OPTIONAL_REVANCED_SETTINGS_PREFIX)) {
|
||||||
|
return key.substring(OPTIONAL_REVANCED_SETTINGS_PREFIX.length());
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the value stored using the import/export key. Do not set any values in this method.
|
||||||
|
*/
|
||||||
|
protected abstract T readFromJSON(JSONObject json, String importExportKey) throws JSONException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves this instance to JSON.
|
||||||
|
* <p>
|
||||||
|
* To keep the JSON simple and readable,
|
||||||
|
* subclasses should not write out any embedded types (such as JSON Array or Dictionaries).
|
||||||
|
* <p>
|
||||||
|
* If this instance is not a type supported natively by JSON (ie: it's not a String/Integer/Float/Long),
|
||||||
|
* then subclasses can override this method and write out a String value representing the value.
|
||||||
|
*/
|
||||||
|
protected void writeToJSON(JSONObject json, String importExportKey) throws JSONException {
|
||||||
|
json.put(importExportKey, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String exportToJson(@Nullable Context alertDialogContext) {
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject();
|
||||||
|
for (Setting<?> setting : allLoadedSettingsSorted()) {
|
||||||
|
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.
|
||||||
|
//noinspection ConstantValue
|
||||||
|
if (setting.includeWithImportExport && (!setting.isSetToDefault() || exportDefaultValues)) {
|
||||||
|
setting.writeToJSON(json, importExportKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SponsorBlockSettings.showExportWarningIfNeeded(alertDialogContext);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Logger.printException(() -> "Export failure", e); // should never happen
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return if any settings that require a reboot were changed.
|
||||||
|
*/
|
||||||
|
public static boolean importFromJSON(@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 (Setting setting : SETTINGS) {
|
||||||
|
String key = setting.getImportExportKey();
|
||||||
|
if (json.has(key)) {
|
||||||
|
Object value = setting.readFromJSON(json, key);
|
||||||
|
if (!setting.get().equals(value)) {
|
||||||
|
rebootSettingChanged |= setting.rebootApp;
|
||||||
|
//noinspection unchecked
|
||||||
|
setting.save(value);
|
||||||
|
}
|
||||||
|
numberOfSettingsImported++;
|
||||||
|
} else if (setting.includeWithImportExport && !setting.isSetToDefault()) {
|
||||||
|
Logger.printDebug(() -> "Resetting to default: " + setting);
|
||||||
|
rebootSettingChanged |= setting.rebootApp;
|
||||||
|
setting.resetToDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SB Enum categories are saved using StringSettings.
|
||||||
|
// Which means they need to reload again if changed by other code (such as here).
|
||||||
|
// This call could be removed by creating a custom Setting class that manages the
|
||||||
|
// "String <-> Enum" logic or by adding an event hook of when settings are imported.
|
||||||
|
// But for now this is simple and works.
|
||||||
|
SponsorBlockSettings.updateFromImportedSettings();
|
||||||
|
|
||||||
|
Utils.showToastLong(numberOfSettingsImported == 0
|
||||||
|
? str("revanced_settings_import_reset")
|
||||||
|
: str("revanced_settings_import_success", numberOfSettingsImported));
|
||||||
|
|
||||||
|
return rebootSettingChanged;
|
||||||
|
} catch (JSONException | IllegalArgumentException ex) {
|
||||||
|
Utils.showToastLong(str("revanced_settings_import_failure_parse", ex.getMessage()));
|
||||||
|
Logger.printInfo(() -> "", ex);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "Import failure: " + ex.getMessage(), ex); // should never happen
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// End import / export
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
package app.revanced.integrations.shared.settings;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class StringSetting extends Setting<String> {
|
||||||
|
|
||||||
|
public StringSetting(String key, String defaultValue) {
|
||||||
|
super(key, defaultValue);
|
||||||
|
}
|
||||||
|
public StringSetting(String key, String defaultValue, boolean rebootApp) {
|
||||||
|
super(key, defaultValue, rebootApp);
|
||||||
|
}
|
||||||
|
public StringSetting(String key, String defaultValue, boolean rebootApp, boolean includeWithImportExport) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport);
|
||||||
|
}
|
||||||
|
public StringSetting(String key, String defaultValue, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, userDialogMessage);
|
||||||
|
}
|
||||||
|
public StringSetting(String key, String defaultValue, Availability availability) {
|
||||||
|
super(key, defaultValue, availability);
|
||||||
|
}
|
||||||
|
public StringSetting(String key, String defaultValue, boolean rebootApp, String userDialogMessage) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage);
|
||||||
|
}
|
||||||
|
public StringSetting(String key, String defaultValue, boolean rebootApp, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, availability);
|
||||||
|
}
|
||||||
|
public StringSetting(String key, String defaultValue, boolean rebootApp, String userDialogMessage, Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
public StringSetting(@NonNull String key, @NonNull String defaultValue, boolean rebootApp, boolean includeWithImportExport, @Nullable String userDialogMessage, @Nullable Availability availability) {
|
||||||
|
super(key, defaultValue, rebootApp, includeWithImportExport, userDialogMessage, availability);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void load() {
|
||||||
|
value = preferences.getString(key, defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String readFromJSON(JSONObject json, String importExportKey) throws JSONException {
|
||||||
|
return json.getString(importExportKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setValueFromString(@NonNull String newValue) {
|
||||||
|
value = Objects.requireNonNull(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(@NonNull String newValue) {
|
||||||
|
// Must set before saving to preferences (otherwise importing fails to update UI correctly).
|
||||||
|
value = Objects.requireNonNull(newValue);
|
||||||
|
preferences.saveString(key, newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,241 @@
|
|||||||
|
package app.revanced.integrations.shared.settings.preference;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.preference.*;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||||
|
import app.revanced.integrations.shared.settings.Setting;
|
||||||
|
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @noinspection deprecation, DataFlowIssue , unused */
|
||||||
|
public abstract class AbstractPreferenceFragment extends PreferenceFragment {
|
||||||
|
/**
|
||||||
|
* Indicates that if a preference changes,
|
||||||
|
* to apply the change from the Setting to the UI component.
|
||||||
|
*/
|
||||||
|
public static boolean settingImportInProgress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to prevent showing reboot dialog, if user cancels a setting user dialog.
|
||||||
|
*/
|
||||||
|
private boolean showingUserDialogMessage;
|
||||||
|
|
||||||
|
private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> {
|
||||||
|
try {
|
||||||
|
Setting<?> setting = Setting.getSettingFromPath(str);
|
||||||
|
if (setting == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Preference pref = findPreference(str);
|
||||||
|
if (pref == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Logger.printDebug(() -> "Preference changed: " + setting.key);
|
||||||
|
|
||||||
|
// Apply 'Setting <- Preference', unless during importing when it needs to be 'Setting -> Preference'.
|
||||||
|
updatePreference(pref, setting, true, settingImportInProgress);
|
||||||
|
// Update any other preference availability that may now be different.
|
||||||
|
updateUIAvailability();
|
||||||
|
|
||||||
|
if (settingImportInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!showingUserDialogMessage) {
|
||||||
|
if (setting.userDialogMessage != null && ((SwitchPreference) pref).isChecked() != (Boolean) setting.defaultValue) {
|
||||||
|
showSettingUserDialogConfirmation((SwitchPreference) pref, (BooleanSetting) setting);
|
||||||
|
} else if (setting.rebootApp) {
|
||||||
|
showRestartDialog(getContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "OnSharedPreferenceChangeListener failure", ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize this instance, and do any custom behavior.
|
||||||
|
* <p>
|
||||||
|
* To ensure all {@link Setting} instances are correctly synced to the UI,
|
||||||
|
* it is important that subclasses make a call or otherwise reference their Settings class bundle
|
||||||
|
* so all app specific {@link Setting} instances are loaded before this method returns.
|
||||||
|
*/
|
||||||
|
protected void initialize() {
|
||||||
|
final var identifier = Utils.getResourceIdentifier("revanced_prefs", "xml");
|
||||||
|
|
||||||
|
if (identifier == 0) return;
|
||||||
|
addPreferencesFromResource(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSettingUserDialogConfirmation(SwitchPreference switchPref, BooleanSetting setting) {
|
||||||
|
final var context = getContext();
|
||||||
|
|
||||||
|
showingUserDialogMessage = true;
|
||||||
|
new AlertDialog.Builder(context)
|
||||||
|
.setTitle(str("revanced_settings_confirm_user_dialog_title"))
|
||||||
|
.setMessage(setting.userDialogMessage.toString())
|
||||||
|
.setPositiveButton(android.R.string.ok, (dialog, id) -> {
|
||||||
|
if (setting.rebootApp) {
|
||||||
|
showRestartDialog(context);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, (dialog, id) -> {
|
||||||
|
switchPref.setChecked(setting.defaultValue); // Recursive call that resets the Setting value.
|
||||||
|
})
|
||||||
|
.setOnDismissListener(dialog -> {
|
||||||
|
showingUserDialogMessage = false;
|
||||||
|
})
|
||||||
|
.setCancelable(false)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates all Preferences values and their availability using the current values in {@link Setting}.
|
||||||
|
*/
|
||||||
|
protected void updateUIToSettingValues() {
|
||||||
|
updatePreferenceScreen(getPreferenceScreen(), true,true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates Preferences availability only using the status of {@link Setting}.
|
||||||
|
*/
|
||||||
|
protected void updateUIAvailability() {
|
||||||
|
updatePreferenceScreen(getPreferenceScreen(), false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Syncs all UI Preferences to any {@link Setting} they represent.
|
||||||
|
*/
|
||||||
|
private void updatePreferenceScreen(@NonNull PreferenceScreen screen,
|
||||||
|
boolean syncSettingValue,
|
||||||
|
boolean applySettingToPreference) {
|
||||||
|
// Alternatively this could iterate thru all Settings and check for any matching Preferences,
|
||||||
|
// but there are many more Settings than UI preferences so it's more efficient to only check
|
||||||
|
// the Preferences.
|
||||||
|
for (int i = 0, prefCount = screen.getPreferenceCount(); i < prefCount; i++) {
|
||||||
|
Preference pref = screen.getPreference(i);
|
||||||
|
if (pref instanceof PreferenceScreen) {
|
||||||
|
updatePreferenceScreen((PreferenceScreen) pref, syncSettingValue, applySettingToPreference);
|
||||||
|
} else if (pref.hasKey()) {
|
||||||
|
String key = pref.getKey();
|
||||||
|
Setting<?> setting = Setting.getSettingFromPath(key);
|
||||||
|
if (setting != null) {
|
||||||
|
updatePreference(pref, setting, syncSettingValue, applySettingToPreference);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a UI Preference with the {@link Setting} that backs it.
|
||||||
|
* If needed, subclasses can override this to handle additional UI Preference types.
|
||||||
|
*
|
||||||
|
* @param syncSetting If the UI should be synced {@link Setting} <-> Preference
|
||||||
|
* @param applySettingToPreference If true, then apply {@link Setting} -> Preference.
|
||||||
|
* If false, then apply {@link Setting} <- Preference.
|
||||||
|
*/
|
||||||
|
protected void updatePreference(@NonNull Preference pref, @NonNull Setting<?> setting,
|
||||||
|
boolean syncSetting, boolean applySettingToPreference) {
|
||||||
|
if (!syncSetting && applySettingToPreference) {
|
||||||
|
throw new InvalidParameterException();
|
||||||
|
}
|
||||||
|
if (syncSetting) {
|
||||||
|
if (pref instanceof SwitchPreference) {
|
||||||
|
SwitchPreference switchPref = (SwitchPreference) pref;
|
||||||
|
BooleanSetting boolSetting = (BooleanSetting) setting;
|
||||||
|
if (applySettingToPreference) {
|
||||||
|
switchPref.setChecked(boolSetting.get());
|
||||||
|
} else {
|
||||||
|
BooleanSetting.privateSetValue(boolSetting, switchPref.isChecked());
|
||||||
|
}
|
||||||
|
} else if (pref instanceof EditTextPreference) {
|
||||||
|
EditTextPreference editPreference = (EditTextPreference) pref;
|
||||||
|
if (applySettingToPreference) {
|
||||||
|
editPreference.getEditText().setText(setting.get().toString());
|
||||||
|
} else {
|
||||||
|
Setting.privateSetValueFromString(setting, editPreference.getText());
|
||||||
|
}
|
||||||
|
} else if (pref instanceof ListPreference) {
|
||||||
|
ListPreference listPref = (ListPreference) pref;
|
||||||
|
if (applySettingToPreference) {
|
||||||
|
listPref.setValue(setting.get().toString());
|
||||||
|
} else {
|
||||||
|
Setting.privateSetValueFromString(setting, listPref.getValue());
|
||||||
|
}
|
||||||
|
updateListPreferenceSummary(listPref, setting);
|
||||||
|
} else {
|
||||||
|
Logger.printException(() -> "Setting cannot be handled: " + pref.getClass() + ": " + pref);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updatePreferenceAvailability(pref, setting);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updatePreferenceAvailability(@NonNull Preference pref, @NonNull Setting<?> setting) {
|
||||||
|
pref.setEnabled(setting.isAvailable());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void updateListPreferenceSummary(ListPreference listPreference, Setting<?> setting) {
|
||||||
|
String objectStringValue = setting.get().toString();
|
||||||
|
final int entryIndex = listPreference.findIndexOfValue(objectStringValue);
|
||||||
|
if (entryIndex >= 0) {
|
||||||
|
listPreference.setSummary(listPreference.getEntries()[entryIndex]);
|
||||||
|
} else {
|
||||||
|
// Value is not an available option.
|
||||||
|
// User manually edited import data, or options changed and current selection is no longer available.
|
||||||
|
// Still show the value in the summary, so it's clear that something is selected.
|
||||||
|
listPreference.setSummary(objectStringValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void showRestartDialog(@NonNull final Context context) {
|
||||||
|
String positiveButton = str("revanced_settings_restart");
|
||||||
|
|
||||||
|
new AlertDialog.Builder(context).setMessage(str("revanced_settings_restart_title"))
|
||||||
|
.setPositiveButton(positiveButton, (dialog, id) -> {
|
||||||
|
Utils.restartApp(context);
|
||||||
|
})
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.setCancelable(false)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("ResourceType")
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
try {
|
||||||
|
PreferenceManager preferenceManager = getPreferenceManager();
|
||||||
|
preferenceManager.setSharedPreferencesName(Setting.preferences.name);
|
||||||
|
|
||||||
|
// Must initialize before adding change listener,
|
||||||
|
// otherwise the syncing of Setting -> UI
|
||||||
|
// causes a callback to the listener even though nothing changed.
|
||||||
|
initialize();
|
||||||
|
updateUIToSettingValues();
|
||||||
|
|
||||||
|
preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(listener);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Logger.printException(() -> "onCreate() failure", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,4 @@
|
|||||||
package app.revanced.integrations.settingsmenu;
|
package app.revanced.integrations.shared.settings.preference;
|
||||||
|
|
||||||
import static app.revanced.integrations.utils.StringRef.str;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -11,11 +9,13 @@ import android.text.InputType;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import app.revanced.integrations.shared.settings.Setting;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
|
/** @noinspection deprecation, unused */
|
||||||
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
||||||
|
|
||||||
private String existingSettings;
|
private String existingSettings;
|
||||||
@ -55,10 +55,10 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
|||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
try {
|
try {
|
||||||
// Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened.
|
// Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened.
|
||||||
existingSettings = SettingsEnum.exportJSON(getContext());
|
existingSettings = Setting.exportToJson(getContext());
|
||||||
getEditText().setText(existingSettings);
|
getEditText().setText(existingSettings);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "showDialog failure", ex);
|
Logger.printException(() -> "showDialog failure", ex);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -68,12 +68,12 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
|||||||
try {
|
try {
|
||||||
// Show the user the settings in JSON format.
|
// Show the user the settings in JSON format.
|
||||||
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> {
|
builder.setNeutralButton(str("revanced_settings_import_copy"), (dialog, which) -> {
|
||||||
ReVancedUtils.setClipboard(getEditText().getText().toString());
|
Utils.setClipboard(getEditText().getText().toString());
|
||||||
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> {
|
}).setPositiveButton(str("revanced_settings_import"), (dialog, which) -> {
|
||||||
importSettings(getEditText().getText().toString());
|
importSettings(getEditText().getText().toString());
|
||||||
});
|
});
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "onPrepareDialogBuilder failure", ex);
|
Logger.printException(() -> "onPrepareDialogBuilder failure", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,15 +82,15 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
|||||||
if (replacementSettings.equals(existingSettings)) {
|
if (replacementSettings.equals(existingSettings)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ReVancedSettingsFragment.settingImportInProgress = true;
|
AbstractPreferenceFragment.settingImportInProgress = true;
|
||||||
final boolean rebootNeeded = SettingsEnum.importJSON(replacementSettings);
|
final boolean rebootNeeded = Setting.importFromJSON(replacementSettings);
|
||||||
if (rebootNeeded) {
|
if (rebootNeeded) {
|
||||||
ReVancedSettingsFragment.showRestartDialog(getContext());
|
AbstractPreferenceFragment.showRestartDialog(getContext());
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "importSettings failure", ex);
|
Logger.printException(() -> "importSettings failure", ex);
|
||||||
} finally {
|
} finally {
|
||||||
ReVancedSettingsFragment.settingImportInProgress = false;
|
AbstractPreferenceFragment.settingImportInProgress = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,4 @@
|
|||||||
package app.revanced.integrations.settingsmenu;
|
package app.revanced.integrations.shared.settings.preference;
|
||||||
|
|
||||||
import static app.revanced.integrations.utils.StringRef.str;
|
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -9,12 +7,14 @@ import android.preference.EditTextPreference;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import app.revanced.integrations.shared.settings.Setting;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.integrations.settings.SettingsEnum;
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class ResettableEditTextPreference extends EditTextPreference {
|
public class ResettableEditTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||||
@ -33,7 +33,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||||||
@Override
|
@Override
|
||||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||||
super.onPrepareDialogBuilder(builder);
|
super.onPrepareDialogBuilder(builder);
|
||||||
SettingsEnum setting = SettingsEnum.settingFromPath(getKey());
|
Setting setting = Setting.getSettingFromPath(getKey());
|
||||||
if (setting != null) {
|
if (setting != null) {
|
||||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
||||||
}
|
}
|
||||||
@ -50,13 +50,13 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
|||||||
}
|
}
|
||||||
button.setOnClickListener(v -> {
|
button.setOnClickListener(v -> {
|
||||||
try {
|
try {
|
||||||
SettingsEnum setting = Objects.requireNonNull(SettingsEnum.settingFromPath(getKey()));
|
Setting setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
||||||
String defaultStringValue = setting.defaultValue.toString();
|
String defaultStringValue = setting.defaultValue.toString();
|
||||||
EditText editText = getEditText();
|
EditText editText = getEditText();
|
||||||
editText.setText(defaultStringValue);
|
editText.setText(defaultStringValue);
|
||||||
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException(() -> "reset failure", ex);
|
Logger.printException(() -> "reset failure", ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
@ -1,43 +1,37 @@
|
|||||||
package app.revanced.integrations.settings;
|
package app.revanced.integrations.shared.settings.preference;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.preference.PreferenceFragment;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import app.revanced.integrations.utils.LogHelper;
|
|
||||||
import app.revanced.integrations.utils.ReVancedUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared categories, and helper methods.
|
* Shared categories, and helper methods.
|
||||||
*
|
*
|
||||||
* The various save methods store numbers as Strings,
|
* The various save methods store numbers as Strings,
|
||||||
* which is required if using {@link android.preference.PreferenceFragment}.
|
* which is required if using {@link PreferenceFragment}.
|
||||||
*
|
*
|
||||||
* If saved numbers will not be used with a preference fragment,
|
* If saved numbers will not be used with a preference fragment,
|
||||||
* then store the primitive numbers using {@link #preferences}.
|
* then store the primitive numbers using the {@link #preferences} itself.
|
||||||
*/
|
*/
|
||||||
public enum SharedPrefCategory {
|
public class SharedPrefCategory {
|
||||||
YOUTUBE("youtube"),
|
|
||||||
RETURN_YOUTUBE_DISLIKE("ryd"),
|
|
||||||
SPONSOR_BLOCK("sponsor-block"),
|
|
||||||
REVANCED_PREFS("revanced_prefs");
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public final String prefName;
|
public final String name;
|
||||||
@NonNull
|
@NonNull
|
||||||
public final SharedPreferences preferences;
|
public final SharedPreferences preferences;
|
||||||
|
|
||||||
SharedPrefCategory(@NonNull String prefName) {
|
public SharedPrefCategory(@NonNull String name) {
|
||||||
this.prefName = Objects.requireNonNull(prefName);
|
this.name = Objects.requireNonNull(name);
|
||||||
preferences = Objects.requireNonNull(ReVancedUtils.getContext()).getSharedPreferences(prefName, Context.MODE_PRIVATE);
|
preferences = Objects.requireNonNull(Utils.getContext()).getSharedPreferences(name, Context.MODE_PRIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeConflictingPreferenceKeyValue(@NonNull String key) {
|
private void removeConflictingPreferenceKeyValue(@NonNull String key) {
|
||||||
LogHelper.printException(() -> "Found conflicting preference: " + key);
|
Logger.printException(() -> "Found conflicting preference: " + key);
|
||||||
preferences.edit().remove(key).apply();
|
preferences.edit().remove(key).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,7 +83,6 @@ public enum SharedPrefCategory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean getBoolean(@NonNull String key, boolean _default) {
|
public boolean getBoolean(@NonNull String key, boolean _default) {
|
||||||
try {
|
try {
|
||||||
return preferences.getBoolean(key, _default);
|
return preferences.getBoolean(key, _default);
|
||||||
@ -159,6 +152,6 @@ public enum SharedPrefCategory {
|
|||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return prefName;
|
return name;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package app.revanced.integrations.tiktok;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.settings.StringSetting;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
// Edit: This could be handled using a custom Setting<Long[]> class
|
||||||
|
// that saves its value to preferences and JSON using the formatted String created here.
|
||||||
|
public static long[] parseMinMax(StringSetting setting) {
|
||||||
|
final String[] minMax = setting.get().split("-");
|
||||||
|
if (minMax.length == 2) {
|
||||||
|
try {
|
||||||
|
final long min = Long.parseLong(minMax[0]);
|
||||||
|
final long max = Long.parseLong(minMax[1]);
|
||||||
|
|
||||||
|
if (min <= max && min >= 0) return new long[]{min, max};
|
||||||
|
|
||||||
|
} catch (NumberFormatException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setting.save("0-" + Long.MAX_VALUE);
|
||||||
|
return new long[]{0L, Long.MAX_VALUE};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package app.revanced.integrations.tiktok.cleardisplay;
|
||||||
|
|
||||||
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class RememberClearDisplayPatch {
|
||||||
|
public static boolean getClearDisplayState() {
|
||||||
|
return Settings.CLEAR_DISPLAY.get();
|
||||||
|
}
|
||||||
|
public static void rememberClearDisplayState(boolean newState) {
|
||||||
|
Settings.CLEAR_DISPLAY.save(newState);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package app.revanced.integrations.tiktok.download;
|
||||||
|
|
||||||
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class DownloadsPatch {
|
||||||
|
public static String getDownloadPath() {
|
||||||
|
return Settings.DOWNLOAD_PATH.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean shouldRemoveWatermark() {
|
||||||
|
return Settings.DOWNLOAD_WATERMARK.get();
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
package app.revanced.tiktok.feedfilter;
|
package app.revanced.integrations.tiktok.feedfilter;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
public class AdsFilter implements IFilter {
|
public class AdsFilter implements IFilter {
|
||||||
@Override
|
@Override
|
||||||
public boolean getEnabled() {
|
public boolean getEnabled() {
|
||||||
return SettingsEnum.REMOVE_ADS.getBoolean();
|
return Settings.REMOVE_ADS.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tiktok.feedfilter;
|
package app.revanced.integrations.tiktok.feedfilter;
|
||||||
|
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
import com.ss.android.ugc.aweme.feed.model.FeedItemList;
|
import com.ss.android.ugc.aweme.feed.model.FeedItemList;
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tiktok.feedfilter;
|
package app.revanced.integrations.tiktok.feedfilter;
|
||||||
|
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
@ -1,12 +1,12 @@
|
|||||||
package app.revanced.tiktok.feedfilter;
|
package app.revanced.integrations.tiktok.feedfilter;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
public class ImageVideoFilter implements IFilter {
|
public class ImageVideoFilter implements IFilter {
|
||||||
@Override
|
@Override
|
||||||
public boolean getEnabled() {
|
public boolean getEnabled() {
|
||||||
return SettingsEnum.HIDE_IMAGE.getBoolean();
|
return Settings.HIDE_IMAGE.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -1,17 +1,17 @@
|
|||||||
package app.revanced.tiktok.feedfilter;
|
package app.revanced.integrations.tiktok.feedfilter;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
||||||
|
|
||||||
import static app.revanced.tiktok.utils.ReVancedUtils.parseMinMax;
|
import static app.revanced.integrations.tiktok.Utils.parseMinMax;
|
||||||
|
|
||||||
public final class LikeCountFilter implements IFilter {
|
public final class LikeCountFilter implements IFilter {
|
||||||
final long minLike;
|
final long minLike;
|
||||||
final long maxLike;
|
final long maxLike;
|
||||||
|
|
||||||
LikeCountFilter() {
|
LikeCountFilter() {
|
||||||
long[] minMax = parseMinMax(SettingsEnum.MIN_MAX_LIKES);
|
long[] minMax = parseMinMax(Settings.MIN_MAX_LIKES);
|
||||||
minLike = minMax[0];
|
minLike = minMax[0];
|
||||||
maxLike = minMax[1];
|
maxLike = minMax[1];
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
package app.revanced.tiktok.feedfilter;
|
package app.revanced.integrations.tiktok.feedfilter;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
public class LiveFilter implements IFilter {
|
public class LiveFilter implements IFilter {
|
||||||
@Override
|
@Override
|
||||||
public boolean getEnabled() {
|
public boolean getEnabled() {
|
||||||
return SettingsEnum.HIDE_LIVE.getBoolean();
|
return Settings.HIDE_LIVE.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -1,12 +1,12 @@
|
|||||||
package app.revanced.tiktok.feedfilter;
|
package app.revanced.integrations.tiktok.feedfilter;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
|
|
||||||
public class StoryFilter implements IFilter {
|
public class StoryFilter implements IFilter {
|
||||||
@Override
|
@Override
|
||||||
public boolean getEnabled() {
|
public boolean getEnabled() {
|
||||||
return SettingsEnum.HIDE_STORY.getBoolean();
|
return Settings.HIDE_STORY.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
@ -1,17 +1,17 @@
|
|||||||
package app.revanced.tiktok.feedfilter;
|
package app.revanced.integrations.tiktok.feedfilter;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
import com.ss.android.ugc.aweme.feed.model.Aweme;
|
||||||
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
import com.ss.android.ugc.aweme.feed.model.AwemeStatistics;
|
||||||
|
|
||||||
import static app.revanced.tiktok.utils.ReVancedUtils.parseMinMax;
|
import static app.revanced.integrations.tiktok.Utils.parseMinMax;
|
||||||
|
|
||||||
public class ViewCountFilter implements IFilter {
|
public class ViewCountFilter implements IFilter {
|
||||||
final long minView;
|
final long minView;
|
||||||
final long maxView;
|
final long maxView;
|
||||||
|
|
||||||
ViewCountFilter() {
|
ViewCountFilter() {
|
||||||
long[] minMax = parseMinMax(SettingsEnum.MIN_MAX_VIEWS);
|
long[] minMax = parseMinMax(Settings.MIN_MAX_VIEWS);
|
||||||
minView = minMax[0];
|
minView = minMax[0];
|
||||||
maxView = minMax[1];
|
maxView = minMax[1];
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tiktok.settingsmenu;
|
package app.revanced.integrations.tiktok.settings;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -7,17 +7,22 @@ import android.preference.PreferenceFragment;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.tiktok.settings.preference.ReVancedPreferenceFragment;
|
||||||
import com.bytedance.ies.ugc.aweme.commercialize.compliance.personalization.AdPersonalizationActivity;
|
import com.bytedance.ies.ugc.aweme.commercialize.compliance.personalization.AdPersonalizationActivity;
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
import app.revanced.tiktok.utils.LogHelper;
|
/**
|
||||||
import app.revanced.tiktok.utils.ReVancedUtils;
|
* Hooks AdPersonalizationActivity.
|
||||||
|
* <p>
|
||||||
|
* This class is responsible for injecting our own fragment by replacing the AdPersonalizationActivity.
|
||||||
public class SettingsMenu {
|
*
|
||||||
|
* @noinspection unused
|
||||||
|
*/
|
||||||
|
public class AdPersonalizationActivityHook {
|
||||||
public static Object createSettingsEntry(String entryClazzName, String entryInfoClazzName) {
|
public static Object createSettingsEntry(String entryClazzName, String entryInfoClazzName) {
|
||||||
try {
|
try {
|
||||||
Class<?> entryClazz = Class.forName(entryClazzName);
|
Class<?> entryClazz = Class.forName(entryClazzName);
|
||||||
@ -26,7 +31,8 @@ public class SettingsMenu {
|
|||||||
Constructor<?> entryInfoConstructor = entryInfoClazz.getDeclaredConstructors()[0];
|
Constructor<?> entryInfoConstructor = entryInfoClazz.getDeclaredConstructors()[0];
|
||||||
Object buttonInfo = entryInfoConstructor.newInstance("ReVanced settings", null, (View.OnClickListener) view -> startSettingsActivity(), "revanced");
|
Object buttonInfo = entryInfoConstructor.newInstance("ReVanced settings", null, (View.OnClickListener) view -> startSettingsActivity(), "revanced");
|
||||||
return entryConstructor.newInstance(buttonInfo);
|
return entryConstructor.newInstance(buttonInfo);
|
||||||
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
|
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException |
|
||||||
|
InstantiationException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,7 +42,7 @@ public class SettingsMenu {
|
|||||||
* @param base The activity to initialize the settings menu on.
|
* @param base The activity to initialize the settings menu on.
|
||||||
* @return Whether the settings menu should be initialized.
|
* @return Whether the settings menu should be initialized.
|
||||||
*/
|
*/
|
||||||
public static boolean initializeSettings(AdPersonalizationActivity base) {
|
public static boolean initialize(AdPersonalizationActivity base) {
|
||||||
Bundle extras = base.getIntent().getExtras();
|
Bundle extras = base.getIntent().getExtras();
|
||||||
if (extras != null && !extras.getBoolean("revanced", false)) return false;
|
if (extras != null && !extras.getBoolean("revanced", false)) return false;
|
||||||
|
|
||||||
@ -63,14 +69,14 @@ public class SettingsMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void startSettingsActivity() {
|
private static void startSettingsActivity() {
|
||||||
Context appContext = ReVancedUtils.getAppContext();
|
Context appContext = Utils.getContext();
|
||||||
if (appContext != null) {
|
if (appContext != null) {
|
||||||
Intent intent = new Intent(appContext, AdPersonalizationActivity.class);
|
Intent intent = new Intent(appContext, AdPersonalizationActivity.class);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
intent.putExtra("revanced", true);
|
intent.putExtra("revanced", true);
|
||||||
appContext.startActivity(intent);
|
appContext.startActivity(intent);
|
||||||
} else {
|
} else {
|
||||||
LogHelper.debug(SettingsMenu.class, "ReVancedUtils.getAppContext() return null");
|
Logger.printDebug(() -> "Utils.getContext() return null");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package app.revanced.integrations.tiktok.settings;
|
||||||
|
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||||
|
import app.revanced.integrations.shared.settings.StringSetting;
|
||||||
|
|
||||||
|
public class Settings extends BaseSettings {
|
||||||
|
public static final BooleanSetting REMOVE_ADS = new BooleanSetting("remove_ads", TRUE, true);
|
||||||
|
public static final BooleanSetting HIDE_LIVE = new BooleanSetting("hide_live", FALSE, true);
|
||||||
|
public static final BooleanSetting HIDE_STORY = new BooleanSetting("hide_story", FALSE, true);
|
||||||
|
public static final BooleanSetting HIDE_IMAGE = new BooleanSetting("hide_image", FALSE, true);
|
||||||
|
public static final StringSetting MIN_MAX_VIEWS = new StringSetting("min_max_views", "0-" + Long.MAX_VALUE, true);
|
||||||
|
public static final StringSetting MIN_MAX_LIKES = new StringSetting("min_max_likes", "0-" + Long.MAX_VALUE, true);
|
||||||
|
public static final StringSetting DOWNLOAD_PATH = new StringSetting("down_path","DCIM/TikTok");
|
||||||
|
public static final BooleanSetting DOWNLOAD_WATERMARK = new BooleanSetting("down_watermark", TRUE);
|
||||||
|
public static final BooleanSetting CLEAR_DISPLAY = new BooleanSetting("clear_display", FALSE);
|
||||||
|
public static final BooleanSetting SIM_SPOOF = new BooleanSetting("simspoof", TRUE, true);
|
||||||
|
public static final StringSetting SIM_SPOOF_ISO = new StringSetting("simspoof_iso", "us");
|
||||||
|
public static final StringSetting SIMSPOOF_MCCMNC = new StringSetting("simspoof_mccmnc", "310160");
|
||||||
|
public static final StringSetting SIMSPOOF_OP_NAME = new StringSetting("simspoof_op_name", "T-Mobile");
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tiktok.settingsmenu;
|
package app.revanced.integrations.tiktok.settings;
|
||||||
|
|
||||||
public class SettingsStatus {
|
public class SettingsStatus {
|
||||||
public static boolean feedFilterEnabled = false;
|
public static boolean feedFilterEnabled = false;
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference;
|
package app.revanced.integrations.tiktok.settings.preference;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -15,7 +15,7 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
import android.widget.RadioGroup;
|
import android.widget.RadioGroup;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.shared.settings.StringSetting;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class DownloadPathPreference extends DialogPreference {
|
public class DownloadPathPreference extends DialogPreference {
|
||||||
@ -27,13 +27,13 @@ public class DownloadPathPreference extends DialogPreference {
|
|||||||
private int mediaPathIndex;
|
private int mediaPathIndex;
|
||||||
private String childDownloadPath;
|
private String childDownloadPath;
|
||||||
|
|
||||||
public DownloadPathPreference(Context context, String title, SettingsEnum setting) {
|
public DownloadPathPreference(Context context, String title, StringSetting setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.setTitle(title);
|
this.setTitle(title);
|
||||||
this.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + setting.getString());
|
this.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + setting.get());
|
||||||
this.setKey(setting.path);
|
this.setKey(setting.key);
|
||||||
this.setValue(setting.getString());
|
this.setValue(setting.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getValue() {
|
public String getValue() {
|
@ -1,17 +1,17 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference;
|
package app.revanced.integrations.tiktok.settings.preference;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.EditTextPreference;
|
import android.preference.EditTextPreference;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.shared.settings.StringSetting;
|
||||||
|
|
||||||
public class InputTextPreference extends EditTextPreference {
|
public class InputTextPreference extends EditTextPreference {
|
||||||
|
|
||||||
public InputTextPreference(Context context, String title, String summary, SettingsEnum setting) {
|
public InputTextPreference(Context context, String title, String summary, StringSetting setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.setTitle(title);
|
this.setTitle(title);
|
||||||
this.setSummary(summary);
|
this.setSummary(summary);
|
||||||
this.setKey(setting.path);
|
this.setKey(setting.key);
|
||||||
this.setText(setting.getString());
|
this.setText(setting.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference;
|
package app.revanced.integrations.tiktok.settings.preference;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -13,7 +13,7 @@ import android.widget.EditText;
|
|||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.shared.settings.StringSetting;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class RangeValuePreference extends DialogPreference {
|
public class RangeValuePreference extends DialogPreference {
|
||||||
@ -27,13 +27,13 @@ public class RangeValuePreference extends DialogPreference {
|
|||||||
|
|
||||||
private boolean mValueSet;
|
private boolean mValueSet;
|
||||||
|
|
||||||
public RangeValuePreference(Context context, String title, String summary, SettingsEnum setting) {
|
public RangeValuePreference(Context context, String title, String summary, StringSetting setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
setTitle(title);
|
setTitle(title);
|
||||||
setSummary(summary);
|
setSummary(summary);
|
||||||
setKey(setting.path);
|
setKey(setting.key);
|
||||||
setValue(setting.getString());
|
setValue(setting.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setValue(String value) {
|
public void setValue(String value) {
|
@ -0,0 +1,29 @@
|
|||||||
|
package app.revanced.integrations.tiktok.settings.preference;
|
||||||
|
|
||||||
|
import android.preference.PreferenceScreen;
|
||||||
|
import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment;
|
||||||
|
import app.revanced.integrations.tiktok.settings.preference.categories.DownloadsPreferenceCategory;
|
||||||
|
import app.revanced.integrations.tiktok.settings.preference.categories.FeedFilterPreferenceCategory;
|
||||||
|
import app.revanced.integrations.tiktok.settings.preference.categories.IntegrationsPreferenceCategory;
|
||||||
|
import app.revanced.integrations.tiktok.settings.preference.categories.SimSpoofPreferenceCategory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference fragment for ReVanced settings
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initialize() {
|
||||||
|
final var context = getContext();
|
||||||
|
|
||||||
|
PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(context);
|
||||||
|
setPreferenceScreen(preferenceScreen);
|
||||||
|
|
||||||
|
// Custom categories reference app specific Settings class.
|
||||||
|
new FeedFilterPreferenceCategory(context, preferenceScreen);
|
||||||
|
new DownloadsPreferenceCategory(context, preferenceScreen);
|
||||||
|
new SimSpoofPreferenceCategory(context, preferenceScreen);
|
||||||
|
new IntegrationsPreferenceCategory(context, preferenceScreen);
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference;
|
package app.revanced.integrations.tiktok.settings.preference;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.SwitchPreference;
|
import android.preference.SwitchPreference;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class TogglePreference extends SwitchPreference {
|
public class TogglePreference extends SwitchPreference {
|
||||||
public TogglePreference(Context context, String title, String summary, SettingsEnum setting) {
|
public TogglePreference(Context context, String title, String summary, BooleanSetting setting) {
|
||||||
super(context);
|
super(context);
|
||||||
this.setTitle(title);
|
this.setTitle(title);
|
||||||
this.setSummary(summary);
|
this.setSummary(summary);
|
||||||
this.setKey(setting.path);
|
this.setKey(setting.key);
|
||||||
this.setChecked(setting.getBoolean());
|
this.setChecked(setting.get());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference.categories;
|
package app.revanced.integrations.tiktok.settings.preference.categories;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.PreferenceCategory;
|
import android.preference.PreferenceCategory;
|
@ -1,11 +1,11 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference.categories;
|
package app.revanced.integrations.tiktok.settings.preference.categories;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
import app.revanced.integrations.tiktok.settings.SettingsStatus;
|
||||||
import app.revanced.tiktok.settingsmenu.preference.DownloadPathPreference;
|
import app.revanced.integrations.tiktok.settings.preference.DownloadPathPreference;
|
||||||
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class DownloadsPreferenceCategory extends ConditionalPreferenceCategory {
|
public class DownloadsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||||
@ -24,12 +24,12 @@ public class DownloadsPreferenceCategory extends ConditionalPreferenceCategory {
|
|||||||
addPreference(new DownloadPathPreference(
|
addPreference(new DownloadPathPreference(
|
||||||
context,
|
context,
|
||||||
"Download path",
|
"Download path",
|
||||||
SettingsEnum.DOWNLOAD_PATH
|
Settings.DOWNLOAD_PATH
|
||||||
));
|
));
|
||||||
addPreference(new TogglePreference(
|
addPreference(new TogglePreference(
|
||||||
context,
|
context,
|
||||||
"Remove watermark", "",
|
"Remove watermark", "",
|
||||||
SettingsEnum.DOWNLOAD_WATERMARK
|
Settings.DOWNLOAD_WATERMARK
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference.categories;
|
package app.revanced.integrations.tiktok.settings.preference.categories;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.preference.RangeValuePreference;
|
||||||
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import app.revanced.tiktok.settingsmenu.preference.RangeValuePreference;
|
import app.revanced.integrations.tiktok.settings.SettingsStatus;
|
||||||
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory {
|
public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory {
|
||||||
@ -24,32 +24,32 @@ public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory
|
|||||||
addPreference(new TogglePreference(
|
addPreference(new TogglePreference(
|
||||||
context,
|
context,
|
||||||
"Remove feed ads", "Remove ads from feed.",
|
"Remove feed ads", "Remove ads from feed.",
|
||||||
SettingsEnum.REMOVE_ADS
|
Settings.REMOVE_ADS
|
||||||
));
|
));
|
||||||
addPreference(new TogglePreference(
|
addPreference(new TogglePreference(
|
||||||
context,
|
context,
|
||||||
"Hide livestreams", "Hide livestreams from feed.",
|
"Hide livestreams", "Hide livestreams from feed.",
|
||||||
SettingsEnum.HIDE_LIVE
|
Settings.HIDE_LIVE
|
||||||
));
|
));
|
||||||
addPreference(new TogglePreference(
|
addPreference(new TogglePreference(
|
||||||
context,
|
context,
|
||||||
"Hide story", "Hide story from feed.",
|
"Hide story", "Hide story from feed.",
|
||||||
SettingsEnum.HIDE_STORY
|
Settings.HIDE_STORY
|
||||||
));
|
));
|
||||||
addPreference(new TogglePreference(
|
addPreference(new TogglePreference(
|
||||||
context,
|
context,
|
||||||
"Hide image video", "Hide image video from feed.",
|
"Hide image video", "Hide image video from feed.",
|
||||||
SettingsEnum.HIDE_IMAGE
|
Settings.HIDE_IMAGE
|
||||||
));
|
));
|
||||||
addPreference(new RangeValuePreference(
|
addPreference(new RangeValuePreference(
|
||||||
context,
|
context,
|
||||||
"Min/Max views", "The minimum or maximum views of a video to show.",
|
"Min/Max views", "The minimum or maximum views of a video to show.",
|
||||||
SettingsEnum.MIN_MAX_VIEWS
|
Settings.MIN_MAX_VIEWS
|
||||||
));
|
));
|
||||||
addPreference(new RangeValuePreference(
|
addPreference(new RangeValuePreference(
|
||||||
context,
|
context,
|
||||||
"Min/Max likes", "The minimum or maximum likes of a video to show.",
|
"Min/Max likes", "The minimum or maximum likes of a video to show.",
|
||||||
SettingsEnum.MIN_MAX_LIKES
|
Settings.MIN_MAX_LIKES
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,10 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference.categories;
|
package app.revanced.integrations.tiktok.settings.preference.categories;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
|
||||||
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class IntegrationsPreferenceCategory extends ConditionalPreferenceCategory {
|
public class IntegrationsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||||
@ -22,7 +23,7 @@ public class IntegrationsPreferenceCategory extends ConditionalPreferenceCategor
|
|||||||
addPreference(new TogglePreference(context,
|
addPreference(new TogglePreference(context,
|
||||||
"Enable debug log",
|
"Enable debug log",
|
||||||
"Show integration debug log.",
|
"Show integration debug log.",
|
||||||
SettingsEnum.DEBUG
|
BaseSettings.DEBUG
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,11 +1,11 @@
|
|||||||
package app.revanced.tiktok.settingsmenu.preference.categories;
|
package app.revanced.integrations.tiktok.settings.preference.categories;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
import app.revanced.integrations.tiktok.settings.SettingsStatus;
|
||||||
import app.revanced.tiktok.settingsmenu.preference.InputTextPreference;
|
import app.revanced.integrations.tiktok.settings.preference.InputTextPreference;
|
||||||
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
public class SimSpoofPreferenceCategory extends ConditionalPreferenceCategory {
|
public class SimSpoofPreferenceCategory extends ConditionalPreferenceCategory {
|
||||||
@ -26,22 +26,22 @@ public class SimSpoofPreferenceCategory extends ConditionalPreferenceCategory {
|
|||||||
context,
|
context,
|
||||||
"Fake sim card info",
|
"Fake sim card info",
|
||||||
"Bypass regional restriction by fake sim card information.",
|
"Bypass regional restriction by fake sim card information.",
|
||||||
SettingsEnum.SIM_SPOOF
|
Settings.SIM_SPOOF
|
||||||
));
|
));
|
||||||
addPreference(new InputTextPreference(
|
addPreference(new InputTextPreference(
|
||||||
context,
|
context,
|
||||||
"Country ISO", "us, uk, jp, ...",
|
"Country ISO", "us, uk, jp, ...",
|
||||||
SettingsEnum.SIM_SPOOF_ISO
|
Settings.SIM_SPOOF_ISO
|
||||||
));
|
));
|
||||||
addPreference(new InputTextPreference(
|
addPreference(new InputTextPreference(
|
||||||
context,
|
context,
|
||||||
"Operator mcc+mnc", "mcc+mnc",
|
"Operator mcc+mnc", "mcc+mnc",
|
||||||
SettingsEnum.SIMSPOOF_MCCMNC
|
Settings.SIMSPOOF_MCCMNC
|
||||||
));
|
));
|
||||||
addPreference(new InputTextPreference(
|
addPreference(new InputTextPreference(
|
||||||
context,
|
context,
|
||||||
"Operator name", "Name of the operator.",
|
"Operator name", "Name of the operator.",
|
||||||
SettingsEnum.SIMSPOOF_OP_NAME
|
Settings.SIMSPOOF_OP_NAME
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,15 @@
|
|||||||
package app.revanced.tiktok.spoof.sim;
|
package app.revanced.integrations.tiktok.spoof.sim;
|
||||||
|
|
||||||
import app.revanced.tiktok.settings.SettingsEnum;
|
import app.revanced.integrations.tiktok.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class SpoofSimPatch {
|
public class SpoofSimPatch {
|
||||||
public static boolean isEnable() {
|
public static boolean isEnable() {
|
||||||
return SettingsEnum.SIM_SPOOF.getBoolean();
|
return Settings.SIM_SPOOF.get();
|
||||||
}
|
}
|
||||||
public static String getCountryIso(String value) {
|
public static String getCountryIso(String value) {
|
||||||
if (isEnable()) {
|
if (isEnable()) {
|
||||||
return SettingsEnum.SIM_SPOOF_ISO.getString();
|
return Settings.SIM_SPOOF_ISO.get();
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -16,14 +17,14 @@ public class SpoofSimPatch {
|
|||||||
}
|
}
|
||||||
public static String getOperator(String value) {
|
public static String getOperator(String value) {
|
||||||
if (isEnable()) {
|
if (isEnable()) {
|
||||||
return SettingsEnum.SIMSPOOF_MCCMNC.getString();
|
return Settings.SIMSPOOF_MCCMNC.get();
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static String getOperatorName(String value) {
|
public static String getOperatorName(String value) {
|
||||||
if (isEnable()) {
|
if (isEnable()) {
|
||||||
return SettingsEnum.SIMSPOOF_OP_NAME.getString();
|
return Settings.SIMSPOOF_OP_NAME.get();
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tudortmund.lockscreen;
|
package app.revanced.integrations.tudortmund.lockscreen;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.tumblr.patches;
|
package app.revanced.integrations.tumblr.patches;
|
||||||
|
|
||||||
import com.tumblr.rumblr.model.TimelineObject;
|
import com.tumblr.rumblr.model.TimelineObject;
|
||||||
import com.tumblr.rumblr.model.Timelineable;
|
import com.tumblr.rumblr.model.Timelineable;
|
@ -0,0 +1,14 @@
|
|||||||
|
package app.revanced.integrations.twitch;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
/* Called from SettingsPatch smali */
|
||||||
|
public static int getStringId(String name) {
|
||||||
|
return app.revanced.integrations.shared.Utils.getResourceIdentifier(name, "string");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Called from SettingsPatch smali */
|
||||||
|
public static int getDrawableId(String name) {
|
||||||
|
return app.revanced.integrations.shared.Utils.getResourceIdentifier(name, "drawable");
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.twitch.adblock;
|
package app.revanced.integrations.twitch.adblock;
|
||||||
|
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
|
@ -1,14 +1,15 @@
|
|||||||
package app.revanced.twitch.adblock;
|
package app.revanced.integrations.twitch.adblock;
|
||||||
|
|
||||||
import app.revanced.twitch.utils.LogHelper;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.twitch.utils.ReVancedUtils;
|
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
public class LuminousService implements IAdblockService {
|
public class LuminousService implements IAdblockService {
|
||||||
@Override
|
@Override
|
||||||
public String friendlyName() {
|
public String friendlyName() {
|
||||||
return ReVancedUtils.getString("revanced_proxy_luminous");
|
return str("revanced_proxy_luminous");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -33,7 +34,7 @@ public class LuminousService implements IAdblockService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
LogHelper.error("Failed to parse rewritten URL");
|
Logger.printException(() -> "Failed to parse rewritten URL");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -1,14 +1,16 @@
|
|||||||
package app.revanced.twitch.adblock;
|
package app.revanced.integrations.twitch.adblock;
|
||||||
|
|
||||||
import app.revanced.twitch.api.RetrofitClient;
|
import app.revanced.integrations.shared.Logger;
|
||||||
import app.revanced.twitch.utils.LogHelper;
|
import app.revanced.integrations.twitch.api.RetrofitClient;
|
||||||
import app.revanced.twitch.utils.ReVancedUtils;
|
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
public class PurpleAdblockService implements IAdblockService {
|
public class PurpleAdblockService implements IAdblockService {
|
||||||
private final Map<String, Boolean> tunnels = new HashMap<>() {{
|
private final Map<String, Boolean> tunnels = new HashMap<>() {{
|
||||||
put("https://eu1.jupter.ga", false);
|
put("https://eu1.jupter.ga", false);
|
||||||
@ -17,7 +19,7 @@ public class PurpleAdblockService implements IAdblockService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String friendlyName() {
|
public String friendlyName() {
|
||||||
return ReVancedUtils.getString("revanced_proxy_purpleadblock");
|
return str("revanced_proxy_purpleadblock");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -33,19 +35,27 @@ public class PurpleAdblockService implements IAdblockService {
|
|||||||
try {
|
try {
|
||||||
var response = RetrofitClient.getInstance().getPurpleAdblockApi().ping(tunnel).execute();
|
var response = RetrofitClient.getInstance().getPurpleAdblockApi().ping(tunnel).execute();
|
||||||
if (!response.isSuccessful()) {
|
if (!response.isSuccessful()) {
|
||||||
LogHelper.error("PurpleAdBlock tunnel $tunnel returned an error: HTTP code %d", response.code());
|
Logger.printException(() ->
|
||||||
LogHelper.debug(response.message());
|
"PurpleAdBlock tunnel $tunnel returned an error: HTTP code " + response.code()
|
||||||
|
);
|
||||||
|
Logger.printDebug(response::message);
|
||||||
|
|
||||||
try (var errorBody = response.errorBody()) {
|
try (var errorBody = response.errorBody()) {
|
||||||
if (errorBody != null) {
|
if (errorBody != null) {
|
||||||
LogHelper.debug(errorBody.string());
|
Logger.printDebug(() -> {
|
||||||
|
try {
|
||||||
|
return errorBody.string();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
LogHelper.printException("PurpleAdBlock tunnel $tunnel is unavailable", ex);
|
Logger.printException(() -> "PurpleAdBlock tunnel $tunnel is unavailable", ex);
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,7 +79,7 @@ public class PurpleAdblockService implements IAdblockService {
|
|||||||
// Compose new URL
|
// Compose new URL
|
||||||
var url = HttpUrl.parse(server + "/channel/" + IAdblockService.channelName(originalRequest));
|
var url = HttpUrl.parse(server + "/channel/" + IAdblockService.channelName(originalRequest));
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
LogHelper.error("Failed to parse rewritten URL");
|
Logger.printException(() -> "Failed to parse rewritten URL");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +90,7 @@ public class PurpleAdblockService implements IAdblockService {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
LogHelper.error("No tunnels are available");
|
Logger.printException(() -> "No tunnels are available");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.twitch.api;
|
package app.revanced.integrations.twitch.api;
|
||||||
|
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
@ -0,0 +1,120 @@
|
|||||||
|
package app.revanced.integrations.twitch.api;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.twitch.adblock.IAdblockService;
|
||||||
|
import app.revanced.integrations.twitch.adblock.LuminousService;
|
||||||
|
import app.revanced.integrations.twitch.adblock.PurpleAdblockService;
|
||||||
|
import app.revanced.integrations.twitch.settings.Settings;
|
||||||
|
import okhttp3.Interceptor;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
|
public class RequestInterceptor implements Interceptor {
|
||||||
|
private IAdblockService activeService = null;
|
||||||
|
|
||||||
|
private static final String PROXY_DISABLED = str("key_revanced_proxy_disabled");
|
||||||
|
private static final String LUMINOUS_SERVICE = str("key_revanced_proxy_luminous");
|
||||||
|
private static final String PURPLE_ADBLOCK_SERVICE = str("key_revanced_proxy_purpleadblock");
|
||||||
|
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Response intercept(@NonNull Chain chain) throws IOException {
|
||||||
|
var originalRequest = chain.request();
|
||||||
|
|
||||||
|
if (Settings.BLOCK_EMBEDDED_ADS.get().equals(PROXY_DISABLED)) {
|
||||||
|
return chain.proceed(originalRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Intercepted request to URL:" + originalRequest.url());
|
||||||
|
|
||||||
|
// Skip if not HLS manifest request
|
||||||
|
if (!originalRequest.url().host().contains("usher.ttvnw.net")) {
|
||||||
|
return chain.proceed(originalRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String isVod;
|
||||||
|
if (IAdblockService.isVod(originalRequest)) isVod = "yes";
|
||||||
|
else isVod = "no";
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Found HLS manifest request. Is VOD? " +
|
||||||
|
isVod +
|
||||||
|
"; Channel: " +
|
||||||
|
IAdblockService.channelName(originalRequest)
|
||||||
|
);
|
||||||
|
|
||||||
|
// None of the services support VODs currently
|
||||||
|
if (IAdblockService.isVod(originalRequest)) return chain.proceed(originalRequest);
|
||||||
|
|
||||||
|
updateActiveService();
|
||||||
|
|
||||||
|
if (activeService != null) {
|
||||||
|
var available = activeService.isAvailable();
|
||||||
|
var rewritten = activeService.rewriteHlsRequest(originalRequest);
|
||||||
|
|
||||||
|
|
||||||
|
if (!available || rewritten == null) {
|
||||||
|
Utils.showToastShort(String.format(
|
||||||
|
str("revanced_embedded_ads_service_unavailable"), activeService.friendlyName()
|
||||||
|
));
|
||||||
|
return chain.proceed(originalRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.printDebug(() -> "Rewritten HLS stream URL: " + rewritten.url());
|
||||||
|
|
||||||
|
var maxAttempts = activeService.maxAttempts();
|
||||||
|
|
||||||
|
for (var i = 1; i <= maxAttempts; i++) {
|
||||||
|
// Execute rewritten request and close body to allow multiple proceed() calls
|
||||||
|
var response = chain.proceed(rewritten);
|
||||||
|
response.close();
|
||||||
|
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
int attempt = i;
|
||||||
|
Logger.printException(() -> "Request failed (attempt " +
|
||||||
|
attempt +
|
||||||
|
"/" + maxAttempts + "): HTTP error " +
|
||||||
|
response.code() +
|
||||||
|
" (" + response.message() + ")"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Logger.printException(() -> "Failed to sleep", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Accept response from ad blocker
|
||||||
|
Logger.printDebug(() -> "Ad-blocker used");
|
||||||
|
return chain.proceed(rewritten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// maxAttempts exceeded; giving up on using the ad blocker
|
||||||
|
Utils.showToastLong(String.format(
|
||||||
|
str("revanced_embedded_ads_service_failed"),
|
||||||
|
activeService.friendlyName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adblock disabled
|
||||||
|
return chain.proceed(originalRequest);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateActiveService() {
|
||||||
|
var current = Settings.BLOCK_EMBEDDED_ADS.get();
|
||||||
|
|
||||||
|
if (current.equals(LUMINOUS_SERVICE) && !(activeService instanceof LuminousService))
|
||||||
|
activeService = new LuminousService();
|
||||||
|
else if (current.equals(PURPLE_ADBLOCK_SERVICE) && !(activeService instanceof PurpleAdblockService))
|
||||||
|
activeService = new PurpleAdblockService();
|
||||||
|
else if (current.equals(PROXY_DISABLED))
|
||||||
|
activeService = null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.twitch.api;
|
package app.revanced.integrations.twitch.api;
|
||||||
|
|
||||||
import retrofit2.Retrofit;
|
import retrofit2.Retrofit;
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.integrations.twitch.patches;
|
||||||
|
|
||||||
|
import app.revanced.integrations.twitch.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class AudioAdsPatch {
|
||||||
|
public static boolean shouldBlockAudioAds() {
|
||||||
|
return Settings.BLOCK_AUDIO_ADS.get();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.integrations.twitch.patches;
|
||||||
|
|
||||||
|
import app.revanced.integrations.twitch.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class AutoClaimChannelPointsPatch {
|
||||||
|
public static boolean shouldAutoClaim() {
|
||||||
|
return Settings.AUTO_CLAIM_CHANNEL_POINTS.get();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.integrations.twitch.patches;
|
||||||
|
|
||||||
|
import app.revanced.integrations.twitch.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class DebugModePatch {
|
||||||
|
public static boolean isDebugModeEnabled() {
|
||||||
|
return Settings.TWITCH_DEBUG_MODE.get();
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,8 @@
|
|||||||
package app.revanced.twitch.patches;
|
package app.revanced.integrations.twitch.patches;
|
||||||
|
|
||||||
import app.revanced.twitch.api.RequestInterceptor;
|
import app.revanced.integrations.twitch.api.RequestInterceptor;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class EmbeddedAdsPatch {
|
public class EmbeddedAdsPatch {
|
||||||
public static RequestInterceptor createRequestInterceptor() {
|
public static RequestInterceptor createRequestInterceptor() {
|
||||||
return new RequestInterceptor();
|
return new RequestInterceptor();
|
@ -1,4 +1,6 @@
|
|||||||
package app.revanced.twitch.patches;
|
package app.revanced.integrations.twitch.patches;
|
||||||
|
|
||||||
|
import static app.revanced.integrations.shared.StringRef.str;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
@ -9,28 +11,29 @@ import android.text.style.ForegroundColorSpan;
|
|||||||
import android.text.style.StrikethroughSpan;
|
import android.text.style.StrikethroughSpan;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
|
|
||||||
import java.util.Objects;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import app.revanced.twitch.settings.SettingsEnum;
|
import app.revanced.integrations.twitch.settings.Settings;
|
||||||
import app.revanced.twitch.utils.ReVancedUtils;
|
|
||||||
import tv.twitch.android.shared.chat.util.ClickableUsernameSpan;
|
import tv.twitch.android.shared.chat.util.ClickableUsernameSpan;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class ShowDeletedMessagesPatch {
|
public class ShowDeletedMessagesPatch {
|
||||||
public static boolean shouldUseSpoiler() {
|
public static boolean shouldUseSpoiler() {
|
||||||
return Objects.equals(SettingsEnum.SHOW_DELETED_MESSAGES.getString(), "spoiler");
|
return "spoiler".equals(Settings.SHOW_DELETED_MESSAGES.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean shouldCrossOut() {
|
public static boolean shouldCrossOut() {
|
||||||
return Objects.equals(SettingsEnum.SHOW_DELETED_MESSAGES.getString(), "cross-out");
|
return "cross-out".equals(Settings.SHOW_DELETED_MESSAGES.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public static Spanned reformatDeletedMessage(Spanned original) {
|
public static Spanned reformatDeletedMessage(Spanned original) {
|
||||||
if (!shouldCrossOut())
|
if (!shouldCrossOut())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
SpannableStringBuilder ssb = new SpannableStringBuilder(original);
|
SpannableStringBuilder ssb = new SpannableStringBuilder(original);
|
||||||
ssb.setSpan(new StrikethroughSpan(), 0, original.length(), 0);
|
ssb.setSpan(new StrikethroughSpan(), 0, original.length(), 0);
|
||||||
ssb.append(" (").append(ReVancedUtils.getString("revanced_deleted_msg")).append(")");
|
ssb.append(" (").append(str("revanced_deleted_msg")).append(")");
|
||||||
ssb.setSpan(new StyleSpan(Typeface.ITALIC), original.length(), ssb.length(), 0);
|
ssb.setSpan(new StyleSpan(Typeface.ITALIC), original.length(), ssb.length(), 0);
|
||||||
|
|
||||||
// Gray-out username
|
// Gray-out username
|
@ -0,0 +1,10 @@
|
|||||||
|
package app.revanced.integrations.twitch.patches;
|
||||||
|
|
||||||
|
import app.revanced.integrations.twitch.settings.Settings;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class VideoAdsPatch {
|
||||||
|
public static boolean shouldBlockVideoAds() {
|
||||||
|
return Settings.BLOCK_VIDEO_ADS.get();
|
||||||
|
}
|
||||||
|
}
|
@ -1,23 +1,24 @@
|
|||||||
package app.revanced.twitch.settingsmenu;
|
package app.revanced.integrations.twitch.settings;
|
||||||
|
|
||||||
import static app.revanced.twitch.utils.ReVancedUtils.getIdentifier;
|
|
||||||
import static app.revanced.twitch.utils.ReVancedUtils.getStringId;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.Utils;
|
||||||
|
import app.revanced.integrations.twitch.settings.preference.ReVancedPreferenceFragment;
|
||||||
|
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
|
||||||
|
import tv.twitch.android.settings.SettingsActivity;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import app.revanced.twitch.utils.ReVancedUtils;
|
/**
|
||||||
import app.revanced.twitch.utils.LogHelper;
|
* Hooks AppCompatActivity.
|
||||||
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
|
* <p>
|
||||||
import tv.twitch.android.settings.SettingsActivity;
|
* This class is responsible for injecting our own fragment by replacing the AppCompatActivity.
|
||||||
|
* @noinspection unused
|
||||||
public class SettingsHooks {
|
*/
|
||||||
|
public class AppCompatActivityHook {
|
||||||
private static final int REVANCED_SETTINGS_MENU_ITEM_ID = 0x7;
|
private static final int REVANCED_SETTINGS_MENU_ITEM_ID = 0x7;
|
||||||
private static final String EXTRA_REVANCED_SETTINGS = "app.revanced.twitch.settings";
|
private static final String EXTRA_REVANCED_SETTINGS = "app.revanced.twitch.settings";
|
||||||
|
|
||||||
@ -25,16 +26,18 @@ public class SettingsHooks {
|
|||||||
* Launches SettingsActivity and show ReVanced settings
|
* Launches SettingsActivity and show ReVanced settings
|
||||||
*/
|
*/
|
||||||
public static void startSettingsActivity() {
|
public static void startSettingsActivity() {
|
||||||
LogHelper.debug("Launching ReVanced settings");
|
Logger.printDebug(() -> "Launching ReVanced settings");
|
||||||
|
|
||||||
ReVancedUtils.ifContextAttached((c) -> {
|
final var context = app.revanced.integrations.shared.Utils.getContext();
|
||||||
Intent intent = new Intent(c, SettingsActivity.class);
|
|
||||||
|
if (context != null) {
|
||||||
|
Intent intent = new Intent(context, SettingsActivity.class);
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putBoolean(EXTRA_REVANCED_SETTINGS, true);
|
bundle.putBoolean(EXTRA_REVANCED_SETTINGS, true);
|
||||||
intent.putExtras(bundle);
|
intent.putExtras(bundle);
|
||||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
c.startActivity(intent);
|
context.startActivity(intent);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,7 +45,7 @@ public class SettingsHooks {
|
|||||||
* @return Returns string resource id
|
* @return Returns string resource id
|
||||||
*/
|
*/
|
||||||
public static int getReVancedSettingsString() {
|
public static int getReVancedSettingsString() {
|
||||||
return getStringId("revanced_settings");
|
return app.revanced.integrations.twitch.Utils.getStringId("revanced_settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,13 +55,12 @@ public class SettingsHooks {
|
|||||||
public static List<SettingsMenuGroup> handleSettingMenuCreation(List<SettingsMenuGroup> settingGroups, Object revancedEntry) {
|
public static List<SettingsMenuGroup> handleSettingMenuCreation(List<SettingsMenuGroup> settingGroups, Object revancedEntry) {
|
||||||
List<SettingsMenuGroup> groups = new ArrayList<>(settingGroups);
|
List<SettingsMenuGroup> groups = new ArrayList<>(settingGroups);
|
||||||
|
|
||||||
if(groups.size() < 1) {
|
if (groups.isEmpty()) {
|
||||||
// Create new menu group if none exist yet
|
// Create new menu group if none exist yet
|
||||||
List<Object> items = new ArrayList<>();
|
List<Object> items = new ArrayList<>();
|
||||||
items.add(revancedEntry);
|
items.add(revancedEntry);
|
||||||
groups.add(new SettingsMenuGroup(items));
|
groups.add(new SettingsMenuGroup(items));
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// Add to last menu group
|
// Add to last menu group
|
||||||
int groupIdx = groups.size() - 1;
|
int groupIdx = groups.size() - 1;
|
||||||
List<Object> items = new ArrayList<>(groups.remove(groupIdx).getSettingsMenuItems());
|
List<Object> items = new ArrayList<>(groups.remove(groupIdx).getSettingsMenuItems());
|
||||||
@ -66,7 +68,7 @@ public class SettingsHooks {
|
|||||||
groups.add(new SettingsMenuGroup(items));
|
groups.add(new SettingsMenuGroup(items));
|
||||||
}
|
}
|
||||||
|
|
||||||
LogHelper.debug("%d menu groups in list", settingGroups.size());
|
Logger.printDebug(() -> settingGroups.size() + " menu groups in list");
|
||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,8 +78,8 @@ public class SettingsHooks {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public static boolean handleSettingMenuOnClick(Enum item) {
|
public static boolean handleSettingMenuOnClick(Enum item) {
|
||||||
LogHelper.debug("item %d clicked", item.ordinal());
|
Logger.printDebug(() -> "item " + item.ordinal() + " clicked");
|
||||||
if(item.ordinal() != REVANCED_SETTINGS_MENU_ITEM_ID) {
|
if (item.ordinal() != REVANCED_SETTINGS_MENU_ITEM_ID) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,21 +91,21 @@ public class SettingsHooks {
|
|||||||
* Intercepts fragment loading in SettingsActivity.onCreate
|
* Intercepts fragment loading in SettingsActivity.onCreate
|
||||||
* @return Returns true if the revanced settings have been requested by the user, otherwise false
|
* @return Returns true if the revanced settings have been requested by the user, otherwise false
|
||||||
*/
|
*/
|
||||||
public static boolean handleSettingsCreation(AppCompatActivity base) {
|
public static boolean handleSettingsCreation(androidx.appcompat.app.AppCompatActivity base) {
|
||||||
if(!base.getIntent().getBooleanExtra(EXTRA_REVANCED_SETTINGS, false)) {
|
if (!base.getIntent().getBooleanExtra(EXTRA_REVANCED_SETTINGS, false)) {
|
||||||
LogHelper.debug("Revanced settings not requested");
|
Logger.printDebug(() -> "Revanced settings not requested");
|
||||||
return false; // User wants to enter another settings fragment
|
return false; // User wants to enter another settings fragment
|
||||||
}
|
}
|
||||||
LogHelper.debug("ReVanced settings requested");
|
Logger.printDebug(() -> "ReVanced settings requested");
|
||||||
|
|
||||||
ReVancedSettingsFragment fragment = new ReVancedSettingsFragment();
|
ReVancedPreferenceFragment fragment = new ReVancedPreferenceFragment();
|
||||||
ActionBar supportActionBar = base.getSupportActionBar();
|
ActionBar supportActionBar = base.getSupportActionBar();
|
||||||
if(supportActionBar != null)
|
if (supportActionBar != null)
|
||||||
supportActionBar.setTitle(getStringId("revanced_settings"));
|
supportActionBar.setTitle(app.revanced.integrations.twitch.Utils.getStringId("revanced_settings"));
|
||||||
|
|
||||||
base.getFragmentManager()
|
base.getFragmentManager()
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
.replace(getIdentifier("fragment_container", "id"), fragment)
|
.replace(Utils.getResourceIdentifier("fragment_container", "id"), fragment)
|
||||||
.commit();
|
.commit();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package app.revanced.integrations.twitch.settings;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||||
|
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||||
|
import app.revanced.integrations.shared.settings.StringSetting;
|
||||||
|
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
|
||||||
|
public class Settings extends BaseSettings {
|
||||||
|
/* Ads */
|
||||||
|
public static final BooleanSetting BLOCK_VIDEO_ADS = new BooleanSetting("revanced_block_video_ads", TRUE);
|
||||||
|
public static final BooleanSetting BLOCK_AUDIO_ADS = new BooleanSetting("revanced_block_audio_ads", TRUE);
|
||||||
|
public static final StringSetting BLOCK_EMBEDDED_ADS = new StringSetting("revanced_block_embedded_ads", "luminous");
|
||||||
|
|
||||||
|
/* Chat */
|
||||||
|
public static final StringSetting SHOW_DELETED_MESSAGES = new StringSetting("revanced_show_deleted_messages", "cross-out");
|
||||||
|
public static final BooleanSetting AUTO_CLAIM_CHANNEL_POINTS = new BooleanSetting("revanced_auto_claim_channel_points", TRUE);
|
||||||
|
|
||||||
|
/* Misc */
|
||||||
|
/**
|
||||||
|
* Not to be confused with {@link BaseSettings#DEBUG}.
|
||||||
|
*/
|
||||||
|
public static final BooleanSetting TWITCH_DEBUG_MODE = new BooleanSetting("revanced_twitch_debug_mode", FALSE, true);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.twitch.settingsmenu.preference;
|
package app.revanced.integrations.twitch.settings.preference;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
@ -0,0 +1,21 @@
|
|||||||
|
package app.revanced.integrations.twitch.settings.preference;
|
||||||
|
|
||||||
|
import app.revanced.integrations.shared.Logger;
|
||||||
|
import app.revanced.integrations.shared.settings.preference.AbstractPreferenceFragment;
|
||||||
|
import app.revanced.integrations.twitch.settings.Settings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preference fragment for ReVanced settings
|
||||||
|
*/
|
||||||
|
public class ReVancedPreferenceFragment extends AbstractPreferenceFragment {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initialize() {
|
||||||
|
super.initialize();
|
||||||
|
|
||||||
|
// Do anything that forces this apps Settings bundle to load.
|
||||||
|
if (Settings.BLOCK_VIDEO_ADS.get()) {
|
||||||
|
Logger.printDebug(() -> "Block video ads enabled"); // Any statement that references the app settings.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.twitter.patches.hook.json
|
package app.revanced.integrations.twitter.patches.hook.json
|
||||||
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
package app.revanced.twitter.patches.hook.json
|
package app.revanced.integrations.twitter.patches.hook.json
|
||||||
|
|
||||||
import app.revanced.twitter.patches.hook.patch.Hook
|
import app.revanced.integrations.twitter.patches.hook.patch.Hook
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
interface JsonHook : Hook<JSONObject> {
|
interface JsonHook : Hook<JSONObject> {
|
@ -1,8 +1,8 @@
|
|||||||
package app.revanced.twitter.patches.hook.json
|
package app.revanced.integrations.twitter.patches.hook.json
|
||||||
|
|
||||||
import app.revanced.twitter.patches.hook.patch.dummy.DummyHook
|
import app.revanced.integrations.twitter.patches.hook.patch.dummy.DummyHook
|
||||||
import app.revanced.twitter.utils.json.JsonUtils.parseJson
|
import app.revanced.integrations.twitter.utils.json.JsonUtils.parseJson
|
||||||
import app.revanced.twitter.utils.stream.StreamUtils
|
import app.revanced.integrations.twitter.utils.stream.StreamUtils
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.twitter.patches.hook.patch
|
package app.revanced.integrations.twitter.patches.hook.patch
|
||||||
|
|
||||||
interface Hook<T> {
|
interface Hook<T> {
|
||||||
/**
|
/**
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.twitter.patches.hook.patch.ads
|
package app.revanced.integrations.twitter.patches.hook.patch.ads
|
||||||
|
|
||||||
import app.revanced.twitter.patches.hook.json.BaseJsonHook
|
import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook
|
||||||
import app.revanced.twitter.patches.hook.twifucker.TwiFucker
|
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.twitter.patches.hook.patch.dummy
|
package app.revanced.integrations.twitter.patches.hook.patch.dummy
|
||||||
|
|
||||||
import app.revanced.twitter.patches.hook.json.BaseJsonHook
|
import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook
|
||||||
import app.revanced.twitter.patches.hook.json.JsonHookPatch
|
import app.revanced.integrations.twitter.patches.hook.json.JsonHookPatch
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
/**
|
@ -1,7 +1,7 @@
|
|||||||
package app.revanced.twitter.patches.hook.patch.recommendation
|
package app.revanced.integrations.twitter.patches.hook.patch.recommendation
|
||||||
|
|
||||||
import app.revanced.twitter.patches.hook.json.BaseJsonHook
|
import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook
|
||||||
import app.revanced.twitter.patches.hook.twifucker.TwiFucker
|
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
|||||||
package app.revanced.twitter.patches.hook.twifucker
|
package app.revanced.integrations.twitter.patches.hook.twifucker
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import app.revanced.twitter.patches.hook.twifucker.TwiFuckerUtils.forEach
|
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFuckerUtils.forEach
|
||||||
import app.revanced.twitter.patches.hook.twifucker.TwiFuckerUtils.forEachIndexed
|
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFuckerUtils.forEachIndexed
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.twitter.patches.hook.twifucker
|
package app.revanced.integrations.twitter.patches.hook.twifucker
|
||||||
|
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
@ -1,6 +1,6 @@
|
|||||||
package app.revanced.twitter.utils.json
|
package app.revanced.integrations.twitter.utils.json
|
||||||
|
|
||||||
import app.revanced.twitter.utils.stream.StreamUtils
|
import app.revanced.integrations.twitter.utils.stream.StreamUtils
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.io.IOException
|
import java.io.IOException
|
@ -1,4 +1,4 @@
|
|||||||
package app.revanced.twitter.utils.stream
|
package app.revanced.integrations.twitter.utils.stream
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
Some files were not shown because too many files have changed in this diff Show More
Loadingβ¦
Reference in New Issue
Block a user