mirror of
https://github.com/revanced/revanced-integrations.git
synced 2024-11-27 22:36:51 +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.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.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.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;
|
||||
|
@ -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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.integrations.shared.settings.BaseSettings;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
|
||||
public class LogHelper {
|
||||
public class Logger {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* 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) {
|
||||
if (SettingsEnum.DEBUG.getBoolean()) {
|
||||
if (DEBUG.get()) {
|
||||
var messageString = message.buildMessageString();
|
||||
|
||||
if (SettingsEnum.DEBUG_STACKTRACE.getBoolean()) {
|
||||
if (DEBUG_STACKTRACE.get()) {
|
||||
var builder = new StringBuilder(messageString);
|
||||
var sw = new StringWriter();
|
||||
new Throwable().printStackTrace(new PrintWriter(sw));
|
||||
@ -125,12 +128,21 @@ public class LogHelper {
|
||||
} else {
|
||||
Log.e(logMessage, messageString, ex);
|
||||
}
|
||||
if (SettingsEnum.DEBUG_TOAST_ON_ERROR.getBoolean()) {
|
||||
if (DEBUG_TOAST_ON_ERROR.get()) {
|
||||
String toastMessageToDisplay = (userToastMessage != null)
|
||||
? userToastMessage
|
||||
: 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.res.Resources;
|
||||
@ -102,7 +102,7 @@ public class StringRef {
|
||||
public String toString() {
|
||||
if (!resolved) {
|
||||
if (resources == null || packageName == null) {
|
||||
Context context = ReVancedUtils.getContext();
|
||||
Context context = Utils.getContext();
|
||||
resources = context.getResources();
|
||||
packageName = context.getPackageName();
|
||||
}
|
||||
@ -110,11 +110,11 @@ public class StringRef {
|
||||
if (resources != null) {
|
||||
final int identifier = resources.getIdentifier(value, "string", packageName);
|
||||
if (identifier == 0)
|
||||
LogHelper.printException(() -> "Resource not found: " + value);
|
||||
Logger.printException(() -> "Resource not found: " + value);
|
||||
else
|
||||
value = resources.getString(identifier);
|
||||
} else {
|
||||
LogHelper.printException(() -> "Could not resolve resources!");
|
||||
Logger.printException(() -> "Could not resolve resources!");
|
||||
}
|
||||
}
|
||||
return value;
|
@ -1,7 +1,8 @@
|
||||
package app.revanced.integrations.utils;
|
||||
package app.revanced.integrations.shared;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
@ -13,24 +14,34 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
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.Nullable;
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
|
||||
import java.text.Bidi;
|
||||
import java.util.Locale;
|
||||
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")
|
||||
public static Context context;
|
||||
|
||||
private static String versionName;
|
||||
|
||||
private ReVancedUtils() {
|
||||
private Utils() {
|
||||
} // utility class
|
||||
|
||||
public static String getVersionName() {
|
||||
@ -51,7 +62,7 @@ public class ReVancedUtils {
|
||||
0
|
||||
);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogHelper.printException(() -> "Failed to get package info", e);
|
||||
Logger.printException(() -> "Failed to get package info", e);
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -64,10 +75,10 @@ public class ReVancedUtils {
|
||||
* @param condition The setting to check for hiding the view.
|
||||
* @param view The view to hide.
|
||||
*/
|
||||
public static void hideViewBy1dpUnderCondition(SettingsEnum condition, View view) {
|
||||
if (!condition.getBoolean()) return;
|
||||
public static void hideViewBy1dpUnderCondition(BooleanSetting condition, View view) {
|
||||
if (!condition.get()) return;
|
||||
|
||||
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
||||
Logger.printDebug(() -> "Hiding view with setting: " + condition);
|
||||
|
||||
hideViewByLayoutParams(view);
|
||||
}
|
||||
@ -78,10 +89,10 @@ public class ReVancedUtils {
|
||||
* @param condition The setting to check for hiding the view.
|
||||
* @param view The view to hide.
|
||||
*/
|
||||
public static void hideViewUnderCondition(SettingsEnum condition, View view) {
|
||||
if (!condition.getBoolean()) return;
|
||||
public static void hideViewUnderCondition(BooleanSetting condition, View view) {
|
||||
if (!condition.get()) return;
|
||||
|
||||
LogHelper.printDebug(() -> "Hiding view with setting: " + condition);
|
||||
Logger.printDebug(() -> "Hiding view with setting: " + condition);
|
||||
|
||||
view.setVisibility(View.GONE);
|
||||
}
|
||||
@ -119,7 +130,7 @@ public class ReVancedUtils {
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public static long doNothingForDuration(long amountOfTimeToWaste) {
|
||||
final long timeCalculationStarted = System.currentTimeMillis();
|
||||
LogHelper.printDebug(() -> "Artificially creating delay of: " + amountOfTimeToWaste + "ms");
|
||||
Logger.printDebug(() -> "Artificially creating delay of: " + amountOfTimeToWaste + "ms");
|
||||
|
||||
long meaninglessValue = 0;
|
||||
while (System.currentTimeMillis() - timeCalculationStarted < amountOfTimeToWaste) {
|
||||
@ -196,16 +207,26 @@ public class ReVancedUtils {
|
||||
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> {
|
||||
boolean matches(T object);
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
if (context != null) {
|
||||
return context;
|
||||
if (context == null) {
|
||||
Logger.initializationError(Utils.class, "Context is null, returning null!", null);
|
||||
}
|
||||
LogHelper.printException(() -> "Context is null, returning null!");
|
||||
return null;
|
||||
return context;
|
||||
}
|
||||
|
||||
public static void setClipboard(@NonNull String text) {
|
||||
@ -249,11 +270,10 @@ public class ReVancedUtils {
|
||||
private static void showToast(@NonNull String messageToToast, int toastDuration) {
|
||||
Objects.requireNonNull(messageToToast);
|
||||
runOnMainThreadNowOrLater(() -> {
|
||||
// cannot use getContext(), otherwise if context is null it will cause infinite recursion of error logging
|
||||
if (context == null) {
|
||||
LogHelper.printDebug(() -> "Cannot show toast (context is null)");
|
||||
Logger.initializationError(Utils.class, "Cannot show toast (context is null): " + messageToToast, null);
|
||||
} else {
|
||||
LogHelper.printDebug(() -> "Showing toast: " + messageToToast);
|
||||
Logger.printDebug(() -> "Showing toast: " + messageToToast);
|
||||
Toast.makeText(context, messageToToast, toastDuration).show();
|
||||
}
|
||||
}
|
||||
@ -277,7 +297,7 @@ public class ReVancedUtils {
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> runnable.getClass() + ": " + ex.getMessage(), ex);
|
||||
Logger.printException(() -> runnable.getClass() + ": " + ex.getMessage(), ex);
|
||||
}
|
||||
};
|
||||
new Handler(Looper.getMainLooper()).postDelayed(loggingRunnable, delayMillis);
|
||||
@ -364,7 +384,7 @@ public class ReVancedUtils {
|
||||
ViewGroup.LayoutParams layoutParams5 = new ViewGroup.LayoutParams(1, 1);
|
||||
view.setLayoutParams(layoutParams5);
|
||||
} else {
|
||||
LogHelper.printDebug(() -> "Hidden view with id " + view.getId());
|
||||
Logger.printDebug(() -> "Hidden view with id " + view.getId());
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
import static app.revanced.integrations.utils.StringRef.str;
|
||||
package app.revanced.integrations.shared.settings.preference;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
@ -11,11 +9,13 @@ import android.text.InputType;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.TypedValue;
|
||||
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 app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
/** @noinspection deprecation, unused */
|
||||
public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener {
|
||||
|
||||
private String existingSettings;
|
||||
@ -55,10 +55,10 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
try {
|
||||
// Must set text before preparing dialog, otherwise text is non selectable if this preference is later reopened.
|
||||
existingSettings = SettingsEnum.exportJSON(getContext());
|
||||
existingSettings = Setting.exportToJson(getContext());
|
||||
getEditText().setText(existingSettings);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "showDialog failure", ex);
|
||||
Logger.printException(() -> "showDialog failure", ex);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -68,12 +68,12 @@ public class ImportExportPreference extends EditTextPreference implements Prefer
|
||||
try {
|
||||
// Show the user the settings in JSON format.
|
||||
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) -> {
|
||||
importSettings(getEditText().getText().toString());
|
||||
});
|
||||
} 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)) {
|
||||
return;
|
||||
}
|
||||
ReVancedSettingsFragment.settingImportInProgress = true;
|
||||
final boolean rebootNeeded = SettingsEnum.importJSON(replacementSettings);
|
||||
AbstractPreferenceFragment.settingImportInProgress = true;
|
||||
final boolean rebootNeeded = Setting.importFromJSON(replacementSettings);
|
||||
if (rebootNeeded) {
|
||||
ReVancedSettingsFragment.showRestartDialog(getContext());
|
||||
AbstractPreferenceFragment.showRestartDialog(getContext());
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "importSettings failure", ex);
|
||||
Logger.printException(() -> "importSettings failure", ex);
|
||||
} finally {
|
||||
ReVancedSettingsFragment.settingImportInProgress = false;
|
||||
AbstractPreferenceFragment.settingImportInProgress = false;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
package app.revanced.integrations.settingsmenu;
|
||||
|
||||
import static app.revanced.integrations.utils.StringRef.str;
|
||||
package app.revanced.integrations.shared.settings.preference;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
@ -9,12 +7,14 @@ import android.preference.EditTextPreference;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import app.revanced.integrations.shared.settings.Setting;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.settings.SettingsEnum;
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ResettableEditTextPreference extends EditTextPreference {
|
||||
|
||||
public ResettableEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
@ -33,7 +33,7 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
||||
@Override
|
||||
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
|
||||
super.onPrepareDialogBuilder(builder);
|
||||
SettingsEnum setting = SettingsEnum.settingFromPath(getKey());
|
||||
Setting setting = Setting.getSettingFromPath(getKey());
|
||||
if (setting != null) {
|
||||
builder.setNeutralButton(str("revanced_settings_reset"), null);
|
||||
}
|
||||
@ -50,13 +50,13 @@ public class ResettableEditTextPreference extends EditTextPreference {
|
||||
}
|
||||
button.setOnClickListener(v -> {
|
||||
try {
|
||||
SettingsEnum setting = Objects.requireNonNull(SettingsEnum.settingFromPath(getKey()));
|
||||
Setting setting = Objects.requireNonNull(Setting.getSettingFromPath(getKey()));
|
||||
String defaultStringValue = setting.defaultValue.toString();
|
||||
EditText editText = getEditText();
|
||||
editText.setText(defaultStringValue);
|
||||
editText.setSelection(defaultStringValue.length()); // move cursor to end of text
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException(() -> "reset failure", ex);
|
||||
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.SharedPreferences;
|
||||
|
||||
import android.preference.PreferenceFragment;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.shared.Utils;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import app.revanced.integrations.utils.LogHelper;
|
||||
import app.revanced.integrations.utils.ReVancedUtils;
|
||||
|
||||
/**
|
||||
* Shared categories, and helper methods.
|
||||
*
|
||||
* 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,
|
||||
* then store the primitive numbers using {@link #preferences}.
|
||||
* then store the primitive numbers using the {@link #preferences} itself.
|
||||
*/
|
||||
public enum SharedPrefCategory {
|
||||
YOUTUBE("youtube"),
|
||||
RETURN_YOUTUBE_DISLIKE("ryd"),
|
||||
SPONSOR_BLOCK("sponsor-block"),
|
||||
REVANCED_PREFS("revanced_prefs");
|
||||
|
||||
public class SharedPrefCategory {
|
||||
@NonNull
|
||||
public final String prefName;
|
||||
public final String name;
|
||||
@NonNull
|
||||
public final SharedPreferences preferences;
|
||||
|
||||
SharedPrefCategory(@NonNull String prefName) {
|
||||
this.prefName = Objects.requireNonNull(prefName);
|
||||
preferences = Objects.requireNonNull(ReVancedUtils.getContext()).getSharedPreferences(prefName, Context.MODE_PRIVATE);
|
||||
public SharedPrefCategory(@NonNull String name) {
|
||||
this.name = Objects.requireNonNull(name);
|
||||
preferences = Objects.requireNonNull(Utils.getContext()).getSharedPreferences(name, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
private void removeConflictingPreferenceKeyValue(@NonNull String key) {
|
||||
LogHelper.printException(() -> "Found conflicting preference: " + key);
|
||||
Logger.printException(() -> "Found conflicting preference: " + key);
|
||||
preferences.edit().remove(key).apply();
|
||||
}
|
||||
|
||||
@ -89,7 +83,6 @@ public enum SharedPrefCategory {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean getBoolean(@NonNull String key, boolean _default) {
|
||||
try {
|
||||
return preferences.getBoolean(key, _default);
|
||||
@ -159,6 +152,6 @@ public enum SharedPrefCategory {
|
||||
@NonNull
|
||||
@Override
|
||||
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;
|
||||
|
||||
public class AdsFilter implements IFilter {
|
||||
@Override
|
||||
public boolean getEnabled() {
|
||||
return SettingsEnum.REMOVE_ADS.getBoolean();
|
||||
return Settings.REMOVE_ADS.get();
|
||||
}
|
||||
|
||||
@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.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;
|
||||
|
@ -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;
|
||||
|
||||
public class ImageVideoFilter implements IFilter {
|
||||
@Override
|
||||
public boolean getEnabled() {
|
||||
return SettingsEnum.HIDE_IMAGE.getBoolean();
|
||||
return Settings.HIDE_IMAGE.get();
|
||||
}
|
||||
|
||||
@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.AwemeStatistics;
|
||||
|
||||
import static app.revanced.tiktok.utils.ReVancedUtils.parseMinMax;
|
||||
import static app.revanced.integrations.tiktok.Utils.parseMinMax;
|
||||
|
||||
public final class LikeCountFilter implements IFilter {
|
||||
final long minLike;
|
||||
final long maxLike;
|
||||
|
||||
LikeCountFilter() {
|
||||
long[] minMax = parseMinMax(SettingsEnum.MIN_MAX_LIKES);
|
||||
long[] minMax = parseMinMax(Settings.MIN_MAX_LIKES);
|
||||
minLike = minMax[0];
|
||||
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;
|
||||
|
||||
public class LiveFilter implements IFilter {
|
||||
@Override
|
||||
public boolean getEnabled() {
|
||||
return SettingsEnum.HIDE_LIVE.getBoolean();
|
||||
return Settings.HIDE_LIVE.get();
|
||||
}
|
||||
|
||||
@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;
|
||||
|
||||
public class StoryFilter implements IFilter {
|
||||
@Override
|
||||
public boolean getEnabled() {
|
||||
return SettingsEnum.HIDE_STORY.getBoolean();
|
||||
return Settings.HIDE_STORY.get();
|
||||
}
|
||||
|
||||
@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.AwemeStatistics;
|
||||
|
||||
import static app.revanced.tiktok.utils.ReVancedUtils.parseMinMax;
|
||||
import static app.revanced.integrations.tiktok.Utils.parseMinMax;
|
||||
|
||||
public class ViewCountFilter implements IFilter {
|
||||
final long minView;
|
||||
final long maxView;
|
||||
|
||||
ViewCountFilter() {
|
||||
long[] minMax = parseMinMax(SettingsEnum.MIN_MAX_VIEWS);
|
||||
long[] minMax = parseMinMax(Settings.MIN_MAX_VIEWS);
|
||||
minView = minMax[0];
|
||||
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.Intent;
|
||||
@ -7,17 +7,22 @@ import android.preference.PreferenceFragment;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
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 java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import app.revanced.tiktok.utils.LogHelper;
|
||||
import app.revanced.tiktok.utils.ReVancedUtils;
|
||||
|
||||
|
||||
public class SettingsMenu {
|
||||
/**
|
||||
* Hooks AdPersonalizationActivity.
|
||||
* <p>
|
||||
* This class is responsible for injecting our own fragment by replacing the AdPersonalizationActivity.
|
||||
*
|
||||
* @noinspection unused
|
||||
*/
|
||||
public class AdPersonalizationActivityHook {
|
||||
public static Object createSettingsEntry(String entryClazzName, String entryInfoClazzName) {
|
||||
try {
|
||||
Class<?> entryClazz = Class.forName(entryClazzName);
|
||||
@ -26,7 +31,8 @@ public class SettingsMenu {
|
||||
Constructor<?> entryInfoConstructor = entryInfoClazz.getDeclaredConstructors()[0];
|
||||
Object buttonInfo = entryInfoConstructor.newInstance("ReVanced settings", null, (View.OnClickListener) view -> startSettingsActivity(), "revanced");
|
||||
return entryConstructor.newInstance(buttonInfo);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException |
|
||||
InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
@ -36,7 +42,7 @@ public class SettingsMenu {
|
||||
* @param base The activity to initialize the settings menu on.
|
||||
* @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();
|
||||
if (extras != null && !extras.getBoolean("revanced", false)) return false;
|
||||
|
||||
@ -63,14 +69,14 @@ public class SettingsMenu {
|
||||
}
|
||||
|
||||
private static void startSettingsActivity() {
|
||||
Context appContext = ReVancedUtils.getAppContext();
|
||||
Context appContext = Utils.getContext();
|
||||
if (appContext != null) {
|
||||
Intent intent = new Intent(appContext, AdPersonalizationActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra("revanced", true);
|
||||
appContext.startActivity(intent);
|
||||
} 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 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.content.Context;
|
||||
@ -15,7 +15,7 @@ import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import app.revanced.tiktok.settings.SettingsEnum;
|
||||
import app.revanced.integrations.shared.settings.StringSetting;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class DownloadPathPreference extends DialogPreference {
|
||||
@ -27,13 +27,13 @@ public class DownloadPathPreference extends DialogPreference {
|
||||
private int mediaPathIndex;
|
||||
private String childDownloadPath;
|
||||
|
||||
public DownloadPathPreference(Context context, String title, SettingsEnum setting) {
|
||||
public DownloadPathPreference(Context context, String title, StringSetting setting) {
|
||||
super(context);
|
||||
this.context = context;
|
||||
this.setTitle(title);
|
||||
this.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + setting.getString());
|
||||
this.setKey(setting.path);
|
||||
this.setValue(setting.getString());
|
||||
this.setSummary(Environment.getExternalStorageDirectory().getPath() + "/" + setting.get());
|
||||
this.setKey(setting.key);
|
||||
this.setValue(setting.get());
|
||||
}
|
||||
|
||||
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.preference.EditTextPreference;
|
||||
|
||||
import app.revanced.tiktok.settings.SettingsEnum;
|
||||
import app.revanced.integrations.shared.settings.StringSetting;
|
||||
|
||||
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);
|
||||
this.setTitle(title);
|
||||
this.setSummary(summary);
|
||||
this.setKey(setting.path);
|
||||
this.setText(setting.getString());
|
||||
this.setKey(setting.key);
|
||||
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.content.Context;
|
||||
@ -13,7 +13,7 @@ import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import app.revanced.tiktok.settings.SettingsEnum;
|
||||
import app.revanced.integrations.shared.settings.StringSetting;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class RangeValuePreference extends DialogPreference {
|
||||
@ -27,13 +27,13 @@ public class RangeValuePreference extends DialogPreference {
|
||||
|
||||
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);
|
||||
this.context = context;
|
||||
setTitle(title);
|
||||
setSummary(summary);
|
||||
setKey(setting.path);
|
||||
setValue(setting.getString());
|
||||
setKey(setting.key);
|
||||
setValue(setting.get());
|
||||
}
|
||||
|
||||
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.preference.SwitchPreference;
|
||||
|
||||
import app.revanced.tiktok.settings.SettingsEnum;
|
||||
import app.revanced.integrations.shared.settings.BooleanSetting;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
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);
|
||||
this.setTitle(title);
|
||||
this.setSummary(summary);
|
||||
this.setKey(setting.path);
|
||||
this.setChecked(setting.getBoolean());
|
||||
this.setKey(setting.key);
|
||||
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.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.preference.PreferenceScreen;
|
||||
import app.revanced.tiktok.settings.SettingsEnum;
|
||||
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
||||
import app.revanced.tiktok.settingsmenu.preference.DownloadPathPreference;
|
||||
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
||||
import app.revanced.integrations.tiktok.settings.Settings;
|
||||
import app.revanced.integrations.tiktok.settings.SettingsStatus;
|
||||
import app.revanced.integrations.tiktok.settings.preference.DownloadPathPreference;
|
||||
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class DownloadsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
@ -24,12 +24,12 @@ public class DownloadsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
addPreference(new DownloadPathPreference(
|
||||
context,
|
||||
"Download path",
|
||||
SettingsEnum.DOWNLOAD_PATH
|
||||
Settings.DOWNLOAD_PATH
|
||||
));
|
||||
addPreference(new TogglePreference(
|
||||
context,
|
||||
"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.preference.PreferenceScreen;
|
||||
import app.revanced.tiktok.settings.SettingsEnum;
|
||||
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
||||
import app.revanced.tiktok.settingsmenu.preference.RangeValuePreference;
|
||||
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
||||
import app.revanced.integrations.tiktok.settings.preference.RangeValuePreference;
|
||||
import app.revanced.integrations.tiktok.settings.Settings;
|
||||
import app.revanced.integrations.tiktok.settings.SettingsStatus;
|
||||
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
@ -24,32 +24,32 @@ public class FeedFilterPreferenceCategory extends ConditionalPreferenceCategory
|
||||
addPreference(new TogglePreference(
|
||||
context,
|
||||
"Remove feed ads", "Remove ads from feed.",
|
||||
SettingsEnum.REMOVE_ADS
|
||||
Settings.REMOVE_ADS
|
||||
));
|
||||
addPreference(new TogglePreference(
|
||||
context,
|
||||
"Hide livestreams", "Hide livestreams from feed.",
|
||||
SettingsEnum.HIDE_LIVE
|
||||
Settings.HIDE_LIVE
|
||||
));
|
||||
addPreference(new TogglePreference(
|
||||
context,
|
||||
"Hide story", "Hide story from feed.",
|
||||
SettingsEnum.HIDE_STORY
|
||||
Settings.HIDE_STORY
|
||||
));
|
||||
addPreference(new TogglePreference(
|
||||
context,
|
||||
"Hide image video", "Hide image video from feed.",
|
||||
SettingsEnum.HIDE_IMAGE
|
||||
Settings.HIDE_IMAGE
|
||||
));
|
||||
addPreference(new RangeValuePreference(
|
||||
context,
|
||||
"Min/Max views", "The minimum or maximum views of a video to show.",
|
||||
SettingsEnum.MIN_MAX_VIEWS
|
||||
Settings.MIN_MAX_VIEWS
|
||||
));
|
||||
addPreference(new RangeValuePreference(
|
||||
context,
|
||||
"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.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")
|
||||
public class IntegrationsPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
@ -22,7 +23,7 @@ public class IntegrationsPreferenceCategory extends ConditionalPreferenceCategor
|
||||
addPreference(new TogglePreference(context,
|
||||
"Enable 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.preference.PreferenceScreen;
|
||||
import app.revanced.tiktok.settings.SettingsEnum;
|
||||
import app.revanced.tiktok.settingsmenu.SettingsStatus;
|
||||
import app.revanced.tiktok.settingsmenu.preference.InputTextPreference;
|
||||
import app.revanced.tiktok.settingsmenu.preference.TogglePreference;
|
||||
import app.revanced.integrations.tiktok.settings.Settings;
|
||||
import app.revanced.integrations.tiktok.settings.SettingsStatus;
|
||||
import app.revanced.integrations.tiktok.settings.preference.InputTextPreference;
|
||||
import app.revanced.integrations.tiktok.settings.preference.TogglePreference;
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public class SimSpoofPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
@ -26,22 +26,22 @@ public class SimSpoofPreferenceCategory extends ConditionalPreferenceCategory {
|
||||
context,
|
||||
"Fake sim card info",
|
||||
"Bypass regional restriction by fake sim card information.",
|
||||
SettingsEnum.SIM_SPOOF
|
||||
Settings.SIM_SPOOF
|
||||
));
|
||||
addPreference(new InputTextPreference(
|
||||
context,
|
||||
"Country ISO", "us, uk, jp, ...",
|
||||
SettingsEnum.SIM_SPOOF_ISO
|
||||
Settings.SIM_SPOOF_ISO
|
||||
));
|
||||
addPreference(new InputTextPreference(
|
||||
context,
|
||||
"Operator mcc+mnc", "mcc+mnc",
|
||||
SettingsEnum.SIMSPOOF_MCCMNC
|
||||
Settings.SIMSPOOF_MCCMNC
|
||||
));
|
||||
addPreference(new InputTextPreference(
|
||||
context,
|
||||
"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 static boolean isEnable() {
|
||||
return SettingsEnum.SIM_SPOOF.getBoolean();
|
||||
return Settings.SIM_SPOOF.get();
|
||||
}
|
||||
public static String getCountryIso(String value) {
|
||||
if (isEnable()) {
|
||||
return SettingsEnum.SIM_SPOOF_ISO.getString();
|
||||
return Settings.SIM_SPOOF_ISO.get();
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
@ -16,14 +17,14 @@ public class SpoofSimPatch {
|
||||
}
|
||||
public static String getOperator(String value) {
|
||||
if (isEnable()) {
|
||||
return SettingsEnum.SIMSPOOF_MCCMNC.getString();
|
||||
return Settings.SIMSPOOF_MCCMNC.get();
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
public static String getOperatorName(String value) {
|
||||
if (isEnable()) {
|
||||
return SettingsEnum.SIMSPOOF_OP_NAME.getString();
|
||||
return Settings.SIMSPOOF_OP_NAME.get();
|
||||
} else {
|
||||
return value;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.tudortmund.lockscreen;
|
||||
package app.revanced.integrations.tudortmund.lockscreen;
|
||||
|
||||
import android.content.Context;
|
||||
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.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;
|
||||
|
@ -1,14 +1,15 @@
|
||||
package app.revanced.twitch.adblock;
|
||||
package app.revanced.integrations.twitch.adblock;
|
||||
|
||||
import app.revanced.twitch.utils.LogHelper;
|
||||
import app.revanced.twitch.utils.ReVancedUtils;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
public class LuminousService implements IAdblockService {
|
||||
@Override
|
||||
public String friendlyName() {
|
||||
return ReVancedUtils.getString("revanced_proxy_luminous");
|
||||
return str("revanced_proxy_luminous");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,7 +34,7 @@ public class LuminousService implements IAdblockService {
|
||||
);
|
||||
|
||||
if (url == null) {
|
||||
LogHelper.error("Failed to parse rewritten URL");
|
||||
Logger.printException(() -> "Failed to parse rewritten URL");
|
||||
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.twitch.utils.LogHelper;
|
||||
import app.revanced.twitch.utils.ReVancedUtils;
|
||||
import app.revanced.integrations.shared.Logger;
|
||||
import app.revanced.integrations.twitch.api.RetrofitClient;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Request;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static app.revanced.integrations.shared.StringRef.str;
|
||||
|
||||
public class PurpleAdblockService implements IAdblockService {
|
||||
private final Map<String, Boolean> tunnels = new HashMap<>() {{
|
||||
put("https://eu1.jupter.ga", false);
|
||||
@ -17,7 +19,7 @@ public class PurpleAdblockService implements IAdblockService {
|
||||
|
||||
@Override
|
||||
public String friendlyName() {
|
||||
return ReVancedUtils.getString("revanced_proxy_purpleadblock");
|
||||
return str("revanced_proxy_purpleadblock");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,19 +35,27 @@ public class PurpleAdblockService implements IAdblockService {
|
||||
try {
|
||||
var response = RetrofitClient.getInstance().getPurpleAdblockApi().ping(tunnel).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
LogHelper.error("PurpleAdBlock tunnel $tunnel returned an error: HTTP code %d", response.code());
|
||||
LogHelper.debug(response.message());
|
||||
Logger.printException(() ->
|
||||
"PurpleAdBlock tunnel $tunnel returned an error: HTTP code " + response.code()
|
||||
);
|
||||
Logger.printDebug(response::message);
|
||||
|
||||
try (var errorBody = response.errorBody()) {
|
||||
if (errorBody != null) {
|
||||
LogHelper.debug(errorBody.string());
|
||||
Logger.printDebug(() -> {
|
||||
try {
|
||||
return errorBody.string();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
success = false;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LogHelper.printException("PurpleAdBlock tunnel $tunnel is unavailable", ex);
|
||||
Logger.printException(() -> "PurpleAdBlock tunnel $tunnel is unavailable", ex);
|
||||
success = false;
|
||||
}
|
||||
|
||||
@ -69,7 +79,7 @@ public class PurpleAdblockService implements IAdblockService {
|
||||
// Compose new URL
|
||||
var url = HttpUrl.parse(server + "/channel/" + IAdblockService.channelName(originalRequest));
|
||||
if (url == null) {
|
||||
LogHelper.error("Failed to parse rewritten URL");
|
||||
Logger.printException(() -> "Failed to parse rewritten URL");
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -80,7 +90,7 @@ public class PurpleAdblockService implements IAdblockService {
|
||||
.build();
|
||||
}
|
||||
|
||||
LogHelper.error("No tunnels are available");
|
||||
Logger.printException(() -> "No tunnels are available");
|
||||
return null;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package app.revanced.twitch.api;
|
||||
package app.revanced.integrations.twitch.api;
|
||||
|
||||
import okhttp3.ResponseBody;
|
||||
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;
|
||||
|
@ -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 static RequestInterceptor createRequestInterceptor() {
|
||||
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.Typeface;
|
||||
@ -9,28 +11,29 @@ import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StrikethroughSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
|
||||
import java.util.Objects;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.revanced.twitch.settings.SettingsEnum;
|
||||
import app.revanced.twitch.utils.ReVancedUtils;
|
||||
import app.revanced.integrations.twitch.settings.Settings;
|
||||
import tv.twitch.android.shared.chat.util.ClickableUsernameSpan;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ShowDeletedMessagesPatch {
|
||||
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() {
|
||||
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) {
|
||||
if (!shouldCrossOut())
|
||||
return null;
|
||||
|
||||
SpannableStringBuilder ssb = new SpannableStringBuilder(original);
|
||||
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);
|
||||
|
||||
// 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;
|
||||
|
||||
import static app.revanced.twitch.utils.ReVancedUtils.getIdentifier;
|
||||
import static app.revanced.twitch.utils.ReVancedUtils.getStringId;
|
||||
package app.revanced.integrations.twitch.settings;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
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.List;
|
||||
|
||||
import app.revanced.twitch.utils.ReVancedUtils;
|
||||
import app.revanced.twitch.utils.LogHelper;
|
||||
import tv.twitch.android.feature.settings.menu.SettingsMenuGroup;
|
||||
import tv.twitch.android.settings.SettingsActivity;
|
||||
|
||||
public class SettingsHooks {
|
||||
/**
|
||||
* Hooks AppCompatActivity.
|
||||
* <p>
|
||||
* This class is responsible for injecting our own fragment by replacing the AppCompatActivity.
|
||||
* @noinspection unused
|
||||
*/
|
||||
public class AppCompatActivityHook {
|
||||
private static final int REVANCED_SETTINGS_MENU_ITEM_ID = 0x7;
|
||||
private static final String EXTRA_REVANCED_SETTINGS = "app.revanced.twitch.settings";
|
||||
|
||||
@ -25,16 +26,18 @@ public class SettingsHooks {
|
||||
* Launches SettingsActivity and show ReVanced settings
|
||||
*/
|
||||
public static void startSettingsActivity() {
|
||||
LogHelper.debug("Launching ReVanced settings");
|
||||
Logger.printDebug(() -> "Launching ReVanced settings");
|
||||
|
||||
ReVancedUtils.ifContextAttached((c) -> {
|
||||
Intent intent = new Intent(c, SettingsActivity.class);
|
||||
final var context = app.revanced.integrations.shared.Utils.getContext();
|
||||
|
||||
if (context != null) {
|
||||
Intent intent = new Intent(context, SettingsActivity.class);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putBoolean(EXTRA_REVANCED_SETTINGS, true);
|
||||
intent.putExtras(bundle);
|
||||
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
|
||||
*/
|
||||
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) {
|
||||
List<SettingsMenuGroup> groups = new ArrayList<>(settingGroups);
|
||||
|
||||
if(groups.size() < 1) {
|
||||
if (groups.isEmpty()) {
|
||||
// Create new menu group if none exist yet
|
||||
List<Object> items = new ArrayList<>();
|
||||
items.add(revancedEntry);
|
||||
groups.add(new SettingsMenuGroup(items));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Add to last menu group
|
||||
int groupIdx = groups.size() - 1;
|
||||
List<Object> items = new ArrayList<>(groups.remove(groupIdx).getSettingsMenuItems());
|
||||
@ -66,7 +68,7 @@ public class SettingsHooks {
|
||||
groups.add(new SettingsMenuGroup(items));
|
||||
}
|
||||
|
||||
LogHelper.debug("%d menu groups in list", settingGroups.size());
|
||||
Logger.printDebug(() -> settingGroups.size() + " menu groups in list");
|
||||
return groups;
|
||||
}
|
||||
|
||||
@ -76,7 +78,7 @@ public class SettingsHooks {
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@ -89,21 +91,21 @@ public class SettingsHooks {
|
||||
* Intercepts fragment loading in SettingsActivity.onCreate
|
||||
* @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)) {
|
||||
LogHelper.debug("Revanced settings not requested");
|
||||
Logger.printDebug(() -> "Revanced settings not requested");
|
||||
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();
|
||||
if (supportActionBar != null)
|
||||
supportActionBar.setTitle(getStringId("revanced_settings"));
|
||||
supportActionBar.setTitle(app.revanced.integrations.twitch.Utils.getStringId("revanced_settings"));
|
||||
|
||||
base.getFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(getIdentifier("fragment_container", "id"), fragment)
|
||||
.replace(Utils.getResourceIdentifier("fragment_container", "id"), fragment)
|
||||
.commit();
|
||||
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.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
|
||||
|
@ -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
|
||||
|
||||
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.twitter.utils.json.JsonUtils.parseJson
|
||||
import app.revanced.twitter.utils.stream.StreamUtils
|
||||
import app.revanced.integrations.twitter.patches.hook.patch.dummy.DummyHook
|
||||
import app.revanced.integrations.twitter.utils.json.JsonUtils.parseJson
|
||||
import app.revanced.integrations.twitter.utils.stream.StreamUtils
|
||||
import org.json.JSONException
|
||||
import java.io.IOException
|
||||
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> {
|
||||
/**
|
@ -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.twitter.patches.hook.twifucker.TwiFucker
|
||||
import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook
|
||||
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker
|
||||
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.twitter.patches.hook.json.JsonHookPatch
|
||||
import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook
|
||||
import app.revanced.integrations.twitter.patches.hook.json.JsonHookPatch
|
||||
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.twitter.patches.hook.twifucker.TwiFucker
|
||||
import app.revanced.integrations.twitter.patches.hook.json.BaseJsonHook
|
||||
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFucker
|
||||
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 app.revanced.twitter.patches.hook.twifucker.TwiFuckerUtils.forEach
|
||||
import app.revanced.twitter.patches.hook.twifucker.TwiFuckerUtils.forEachIndexed
|
||||
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFuckerUtils.forEach
|
||||
import app.revanced.integrations.twitter.patches.hook.twifucker.TwiFuckerUtils.forEachIndexed
|
||||
import org.json.JSONArray
|
||||
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.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.JSONObject
|
||||
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.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