SponsorBlock: Added exclusion list feature
Added the ability to add uploaders to a persistent exclusion list by long-pressing the SponsorBlock icon. Segments won't be skipped, but they will still be marked.
This commit is contained in:
parent
dfb94a2503
commit
4cadf54bc2
9 changed files with 164 additions and 24 deletions
|
@ -76,9 +76,11 @@ import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.SerializedCache;
|
import org.schabi.newpipe.util.SerializedCache;
|
||||||
|
import org.schabi.newpipe.util.SponsorBlockMode;
|
||||||
import org.schabi.newpipe.util.VideoSegment;
|
import org.schabi.newpipe.util.VideoSegment;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import io.reactivex.Observable;
|
import io.reactivex.Observable;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
@ -205,7 +207,7 @@ public abstract class BasePlayer implements
|
||||||
private Disposable stateLoader;
|
private Disposable stateLoader;
|
||||||
|
|
||||||
protected int currentState = STATE_PREFLIGHT;
|
protected int currentState = STATE_PREFLIGHT;
|
||||||
private boolean isBlockingSponsors;
|
private SponsorBlockMode sponsorBlockMode = SponsorBlockMode.DISABLED;
|
||||||
|
|
||||||
public BasePlayer(@NonNull final Context context) {
|
public BasePlayer(@NonNull final Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
@ -237,9 +239,6 @@ public abstract class BasePlayer implements
|
||||||
this.renderFactory = new DefaultRenderersFactory(context);
|
this.renderFactory = new DefaultRenderersFactory(context);
|
||||||
|
|
||||||
this.mPrefs = PreferenceManager.getDefaultSharedPreferences(App.getApp());
|
this.mPrefs = PreferenceManager.getDefaultSharedPreferences(App.getApp());
|
||||||
|
|
||||||
isBlockingSponsors = mPrefs.getBoolean(context.getString(R.string.sponsor_block_enable_key),
|
|
||||||
false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setup() {
|
public void setup() {
|
||||||
|
@ -699,11 +698,25 @@ public abstract class BasePlayer implements
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onBlockingSponsorsButtonClicked() called");
|
Log.d(TAG, "onBlockingSponsorsButtonClicked() called");
|
||||||
}
|
}
|
||||||
isBlockingSponsors = !isBlockingSponsors;
|
|
||||||
|
switch (sponsorBlockMode) {
|
||||||
|
case DISABLED:
|
||||||
|
sponsorBlockMode = SponsorBlockMode.ENABLED;
|
||||||
|
break;
|
||||||
|
case ENABLED:
|
||||||
|
sponsorBlockMode = SponsorBlockMode.DISABLED;
|
||||||
|
break;
|
||||||
|
case EXCLUDE:
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isBlockingSponsors() {
|
public SponsorBlockMode getSponsorBlockMode() {
|
||||||
return isBlockingSponsors;
|
return sponsorBlockMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSponsorBlockMode(final SponsorBlockMode mode) {
|
||||||
|
sponsorBlockMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*//////////////////////////////////////////////////////////////////////////
|
/*//////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -731,9 +744,7 @@ public abstract class BasePlayer implements
|
||||||
simpleExoPlayer.getBufferedPercentage()
|
simpleExoPlayer.getBufferedPercentage()
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isBlockingSponsors
|
if (sponsorBlockMode == SponsorBlockMode.ENABLED) {
|
||||||
&& mPrefs.getBoolean(
|
|
||||||
context.getString(R.string.sponsor_block_enable_key), false)) {
|
|
||||||
final VideoSegment segment = getSkippableSegment(currentProgress);
|
final VideoSegment segment = getSkippableSegment(currentProgress);
|
||||||
if (segment == null) {
|
if (segment == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -1165,11 +1176,22 @@ public abstract class BasePlayer implements
|
||||||
initThumbnail(info.getThumbnailUrl());
|
initThumbnail(info.getThumbnailUrl());
|
||||||
registerView();
|
registerView();
|
||||||
|
|
||||||
|
final boolean isSponsorBlockEnabled = mPrefs.getBoolean(
|
||||||
|
context.getString(R.string.sponsor_block_enable_key), false);
|
||||||
|
final Set<String> channelExclusions = mPrefs.getStringSet(
|
||||||
|
context.getString(R.string.sponsor_block_exclusion_list_key), null);
|
||||||
|
|
||||||
|
if (channelExclusions != null && channelExclusions.contains(info.getUploaderName())) {
|
||||||
|
sponsorBlockMode = SponsorBlockMode.EXCLUDE;
|
||||||
|
} else {
|
||||||
|
sponsorBlockMode = isSponsorBlockEnabled
|
||||||
|
? SponsorBlockMode.ENABLED
|
||||||
|
: SponsorBlockMode.DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
if (info.getUrl().startsWith("https://www.youtube.com")) {
|
if (info.getUrl().startsWith("https://www.youtube.com")) {
|
||||||
final String apiUrl = mPrefs
|
final String apiUrl = mPrefs
|
||||||
.getString(context.getString(R.string.sponsor_block_api_url_key), null);
|
.getString(context.getString(R.string.sponsor_block_api_url_key), null);
|
||||||
final boolean isSponsorBlockEnabled = mPrefs
|
|
||||||
.getBoolean(context.getString(R.string.sponsor_block_enable_key), false);
|
|
||||||
|
|
||||||
if (apiUrl != null && !apiUrl.isEmpty() && isSponsorBlockEnabled) {
|
if (apiUrl != null && !apiUrl.isEmpty() && isSponsorBlockEnabled) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -100,8 +100,11 @@ import org.schabi.newpipe.util.KoreUtil;
|
||||||
import org.schabi.newpipe.util.ListHelper;
|
import org.schabi.newpipe.util.ListHelper;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
import org.schabi.newpipe.util.ShareUtils;
|
import org.schabi.newpipe.util.ShareUtils;
|
||||||
|
import org.schabi.newpipe.util.SponsorBlockMode;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static android.content.Context.WINDOW_SERVICE;
|
import static android.content.Context.WINDOW_SERVICE;
|
||||||
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||||
|
@ -409,7 +412,7 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
channelTextView.setVisibility(View.VISIBLE);
|
channelTextView.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
setMuteButton(muteButton, isMuted());
|
setMuteButton(muteButton, isMuted());
|
||||||
setBlockSponsorsButton(blockSponsorsButton, isBlockingSponsors());
|
setBlockSponsorsButton(blockSponsorsButton);
|
||||||
|
|
||||||
animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0);
|
animateRotation(moreOptionsButton, DEFAULT_CONTROLS_DURATION, 0);
|
||||||
}
|
}
|
||||||
|
@ -483,6 +486,7 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
|
|
||||||
if (blockSponsorsButton != null) {
|
if (blockSponsorsButton != null) {
|
||||||
blockSponsorsButton.setOnClickListener(this);
|
blockSponsorsButton.setOnClickListener(this);
|
||||||
|
blockSponsorsButton.setOnLongClickListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
settingsContentObserver = new ContentObserver(new Handler()) {
|
settingsContentObserver = new ContentObserver(new Handler()) {
|
||||||
|
@ -630,6 +634,8 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
|
|
||||||
showHideKodiButton();
|
showHideKodiButton();
|
||||||
|
|
||||||
|
setBlockSponsorsButton(blockSponsorsButton);
|
||||||
|
|
||||||
titleTextView.setText(tag.getMetadata().getName());
|
titleTextView.setText(tag.getMetadata().getName());
|
||||||
channelTextView.setText(tag.getMetadata().getUploaderName());
|
channelTextView.setText(tag.getMetadata().getUploaderName());
|
||||||
|
|
||||||
|
@ -656,13 +662,18 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
@Override
|
@Override
|
||||||
public void onBlockingSponsorsButtonClicked() {
|
public void onBlockingSponsorsButtonClicked() {
|
||||||
super.onBlockingSponsorsButtonClicked();
|
super.onBlockingSponsorsButtonClicked();
|
||||||
setBlockSponsorsButton(blockSponsorsButton, isBlockingSponsors());
|
setBlockSponsorsButton(blockSponsorsButton);
|
||||||
|
|
||||||
Toast.makeText(context,
|
switch (getSponsorBlockMode()) {
|
||||||
isBlockingSponsors()
|
case DISABLED:
|
||||||
? "SponsorBlock enabled"
|
Toast.makeText(context, "SponsorBlock disabled", Toast.LENGTH_SHORT).show();
|
||||||
: "SponsorBlock disabled",
|
break;
|
||||||
Toast.LENGTH_SHORT).show();
|
case ENABLED:
|
||||||
|
Toast.makeText(context, "SponsorBlock enabled", Toast.LENGTH_SHORT).show();
|
||||||
|
break;
|
||||||
|
case EXCLUDE:
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -857,6 +868,43 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
fragmentListener.onMoreOptionsLongClicked();
|
fragmentListener.onMoreOptionsLongClicked();
|
||||||
hideControls(0, 0);
|
hideControls(0, 0);
|
||||||
hideSystemUIIfNeeded();
|
hideSystemUIIfNeeded();
|
||||||
|
} else if (v.getId() == blockSponsorsButton.getId()) {
|
||||||
|
final MediaSourceTag currentMetadata = getCurrentMetadata();
|
||||||
|
if (currentMetadata == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Set<String> channelExclusions =
|
||||||
|
mPrefs.getStringSet(
|
||||||
|
context.getString(R.string.sponsor_block_exclusion_list_key),
|
||||||
|
new HashSet<>());
|
||||||
|
|
||||||
|
final String toastText;
|
||||||
|
|
||||||
|
if (getSponsorBlockMode() == SponsorBlockMode.EXCLUDE) {
|
||||||
|
if (channelExclusions != null) {
|
||||||
|
channelExclusions.remove(currentMetadata.getMetadata().getUploaderName());
|
||||||
|
}
|
||||||
|
|
||||||
|
setSponsorBlockMode(SponsorBlockMode.ENABLED);
|
||||||
|
toastText = "Uploader removed from SponsorBlock exclusion list";
|
||||||
|
} else {
|
||||||
|
if (channelExclusions != null) {
|
||||||
|
channelExclusions.add(currentMetadata.getMetadata().getUploaderName());
|
||||||
|
}
|
||||||
|
|
||||||
|
setSponsorBlockMode(SponsorBlockMode.EXCLUDE);
|
||||||
|
toastText = "Uploader excluded from SponsorBlock";
|
||||||
|
}
|
||||||
|
|
||||||
|
mPrefs.edit()
|
||||||
|
.putStringSet(
|
||||||
|
context.getString(R.string.sponsor_block_exclusion_list_key),
|
||||||
|
channelExclusions)
|
||||||
|
.apply();
|
||||||
|
|
||||||
|
setBlockSponsorsButton(blockSponsorsButton);
|
||||||
|
Toast.makeText(context, toastText, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1605,15 +1653,29 @@ public class VideoPlayerImpl extends VideoPlayer
|
||||||
? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
|
? R.drawable.ic_volume_off_white_24dp : R.drawable.ic_volume_up_white_24dp));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setBlockSponsorsButton(final ImageButton button,
|
protected void setBlockSponsorsButton(final ImageButton button) {
|
||||||
final boolean isBlockingSponsors) {
|
|
||||||
if (button == null) {
|
if (button == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.setImageDrawable(AppCompatResources.getDrawable(service, isBlockingSponsors
|
final SponsorBlockMode sponsorBlockMode = getSponsorBlockMode();
|
||||||
? R.drawable.ic_sponsor_block_enable_white_24dp
|
final int resId;
|
||||||
: R.drawable.ic_sponsor_block_disable_white_24dp));
|
|
||||||
|
switch (sponsorBlockMode) {
|
||||||
|
case DISABLED:
|
||||||
|
resId = R.drawable.ic_sponsor_block_disable_white_24dp;
|
||||||
|
break;
|
||||||
|
case ENABLED:
|
||||||
|
resId = R.drawable.ic_sponsor_block_enable_white_24dp;
|
||||||
|
break;
|
||||||
|
case EXCLUDE:
|
||||||
|
resId = R.drawable.ic_sponsor_block_exclude_white_24dp;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.setImageDrawable(AppCompatResources.getDrawable(service, resId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,6 +40,7 @@ import java.io.ObjectInputStream;
|
||||||
import java.io.ObjectOutputStream;
|
import java.io.ObjectOutputStream;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.zip.ZipFile;
|
import java.util.zip.ZipFile;
|
||||||
|
@ -169,6 +170,19 @@ public class ContentSettingsFragment extends BasePreferenceFragment {
|
||||||
updateDependencies(preference, newValue);
|
updateDependencies(preference, newValue);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final Preference sponsorBlockClearExclusionListPreference =
|
||||||
|
findPreference(getString(R.string.sponsor_block_clear_exclusion_list_key));
|
||||||
|
sponsorBlockClearExclusionListPreference.setOnPreferenceClickListener((Preference p) -> {
|
||||||
|
getPreferenceManager()
|
||||||
|
.getSharedPreferences()
|
||||||
|
.edit()
|
||||||
|
.putStringSet(
|
||||||
|
getString(R.string.sponsor_block_exclusion_list_key), new HashSet<>())
|
||||||
|
.apply();
|
||||||
|
Toast.makeText(getContext(), "Exclusion list cleared", Toast.LENGTH_SHORT).show();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package org.schabi.newpipe.util;
|
||||||
|
|
||||||
|
public enum SponsorBlockMode {
|
||||||
|
DISABLED,
|
||||||
|
ENABLED,
|
||||||
|
EXCLUDE
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,24c-0.5,0 -1,-0.1 -1.4,-0.4C4.1,19.6 0.1,12.7 0,5.1c0,-1 0.5,-2 1.4,-2.5C8,-0.9 16,-0.9 22.6,2.7C23.5,3.1 24,4.1 24,5.1c-0.1,7.6 -4.1,14.5 -10.5,18.5C13,23.9 12.5,24 12,24zM12,0.8c-3.5,0 -7,0.9 -10.2,2.6c-0.6,0.3 -1,1 -1,1.7C0.9,12.4 4.7,19 11,22.9c0.6,0.4 1.4,0.4 2,0c6.3,-3.8 10,-10.5 10.2,-17.8c0,-0.7 -0.4,-1.4 -1,-1.7C19,1.7 15.5,0.8 12,0.8z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.7,4.2C15.6,1 8.4,1 2.3,4.2C2,4.4 1.8,4.7 1.8,5.1c0.1,7.2 3.9,13.4 9.7,17c0.3,0.2 0.7,0.2 1,0c5.7,-3.5 9.6,-9.8 9.7,-17C22.2,4.7 22,4.4 21.7,4.2zM12,15.8c0,0 -5,-4 -5,-6.6c0,-1.7 1.1,-2.7 2.5,-2.7c1.2,0 2.5,1.3 2.5,1.3s1.2,-1.3 2.5,-1.3c1.4,0 2.5,0.9 2.5,2.7C17,11.8 12,15.8 12,15.8z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="#FFFFFF">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M12,24c-0.5,0 -1,-0.1 -1.4,-0.4C4.1,19.6 0.1,12.7 0,5.1c0,-1 0.5,-2 1.4,-2.5C8,-0.9 16,-0.9 22.6,2.7C23.5,3.1 24,4.1 24,5.1c-0.1,7.6 -4.1,14.5 -10.5,18.5C13,23.9 12.5,24 12,24zM12,0.8c-3.5,0 -7,0.9 -10.2,2.6c-0.6,0.3 -1,1 -1,1.7C0.9,12.4 4.7,19 11,22.9c0.6,0.4 1.4,0.4 2,0c6.3,-3.8 10,-10.5 10.2,-17.8c0,-0.7 -0.4,-1.4 -1,-1.7C19,1.7 15.5,0.8 12,0.8z"/>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M21.7,4.2C15.6,1 8.4,1 2.3,4.2C2,4.4 1.8,4.7 1.8,5.1c0.1,7.2 3.9,13.4 9.7,17c0.3,0.2 0.7,0.2 1,0c5.7,-3.5 9.6,-9.8 9.7,-17C22.2,4.7 22,4.4 21.7,4.2zM12,15.8c0,0 -5,-4 -5,-6.6c0,-1.7 1.1,-2.7 2.5,-2.7c1.2,0 2.5,1.3 2.5,1.3s1.2,-1.3 2.5,-1.3c1.4,0 2.5,0.9 2.5,2.7C17,11.8 12,15.8 12,15.8z"/>
|
||||||
|
</vector>
|
|
@ -258,6 +258,8 @@
|
||||||
<string name="sponsor_block_category_self_promo_color_key" translatable="false">sponsor_block_category_self_promo_color</string>
|
<string name="sponsor_block_category_self_promo_color_key" translatable="false">sponsor_block_category_self_promo_color</string>
|
||||||
<string name="sponsor_block_category_non_music_key" translatable="false">sponsor_block_category_music</string>
|
<string name="sponsor_block_category_non_music_key" translatable="false">sponsor_block_category_music</string>
|
||||||
<string name="sponsor_block_category_non_music_color_key" translatable="false">sponsor_block_category_music_color</string>
|
<string name="sponsor_block_category_non_music_color_key" translatable="false">sponsor_block_category_music_color</string>
|
||||||
|
<string name="sponsor_block_exclusion_list_key" translatable="false">sponsor_block_exclusion_list</string>
|
||||||
|
<string name="sponsor_block_clear_exclusion_list_key" translatable="false">sponsor_block_clear_exclusion_list</string>
|
||||||
|
|
||||||
<!-- FileName Downloads -->
|
<!-- FileName Downloads -->
|
||||||
<string name="settings_file_charset_key" translatable="false">file_rename_charset</string>
|
<string name="settings_file_charset_key" translatable="false">file_rename_charset</string>
|
||||||
|
|
|
@ -706,4 +706,6 @@
|
||||||
<string name="sponsor_block_skip_self_promo_message">Skipped unpaid/self promo</string>
|
<string name="sponsor_block_skip_self_promo_message">Skipped unpaid/self promo</string>
|
||||||
<string name="sponsor_block_skip_non_music_message">Skipped non-music</string>
|
<string name="sponsor_block_skip_non_music_message">Skipped non-music</string>
|
||||||
<string name="sponsor_block_toggle_skipping">Toggle skipping sponsors</string>
|
<string name="sponsor_block_toggle_skipping">Toggle skipping sponsors</string>
|
||||||
|
<string name="sponsor_block_clear_exclusion_list_title">Clear Exclusion List</string>
|
||||||
|
<string name="sponsor_block_clear_exclusion_list_summary">Clear the list of uploaders excluded from SponsorBlock.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -164,5 +164,11 @@
|
||||||
android:title="@string/settings_category_sponsor_block_categories_title"
|
android:title="@string/settings_category_sponsor_block_categories_title"
|
||||||
android:summary="@string/settings_category_sponsor_block_categories_summary"/>
|
android:summary="@string/settings_category_sponsor_block_categories_summary"/>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:iconSpaceReserved="false"
|
||||||
|
android:key="@string/sponsor_block_clear_exclusion_list_key"
|
||||||
|
android:summary="@string/sponsor_block_clear_exclusion_list_summary"
|
||||||
|
android:title="@string/sponsor_block_clear_exclusion_list_title"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
Loading…
Reference in a new issue