Option "Show media posts with missing alt text"
This commit is contained in:
@@ -1,9 +1,16 @@
|
|||||||
package org.joinmastodon.android.utils;
|
package org.joinmastodon.android.utils;
|
||||||
|
|
||||||
import static org.joinmastodon.android.model.Filter.FilterAction.*;
|
import static org.joinmastodon.android.model.Filter.FilterAction.HIDE;
|
||||||
import static org.joinmastodon.android.model.Filter.FilterContext.*;
|
import static org.joinmastodon.android.model.Filter.FilterAction.WARN;
|
||||||
import static org.junit.Assert.*;
|
import static org.joinmastodon.android.model.Filter.FilterContext.HOME;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterContext.PUBLIC;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterContext.THREAD;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import android.graphics.drawable.ColorDrawable;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.model.Attachment;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -19,7 +26,9 @@ public class StatusFilterPredicateTest {
|
|||||||
|
|
||||||
private static final Status
|
private static final Status
|
||||||
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
|
hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()),
|
||||||
warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now());
|
warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now()),
|
||||||
|
noAltText = Status.ofFake(null, "display me with a warning", Instant.now()),
|
||||||
|
withAltText = Status.ofFake(null, "display me with a warning", Instant.now());
|
||||||
|
|
||||||
static {
|
static {
|
||||||
hideMeFilter.phrase = "hide me";
|
hideMeFilter.phrase = "hide me";
|
||||||
@@ -29,6 +38,12 @@ public class StatusFilterPredicateTest {
|
|||||||
warnMeFilter.phrase = "warning";
|
warnMeFilter.phrase = "warning";
|
||||||
warnMeFilter.filterAction = WARN;
|
warnMeFilter.filterAction = WARN;
|
||||||
warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
|
warnMeFilter.context = EnumSet.of(PUBLIC, HOME);
|
||||||
|
|
||||||
|
noAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable());
|
||||||
|
withAltText.mediaAttachments = Attachment.createFakeAttachments("fakeurl", new ColorDrawable());
|
||||||
|
for (Attachment mediaAttachment : withAltText.mediaAttachments) {
|
||||||
|
mediaAttachment.description = "Alt Text";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -78,4 +93,16 @@ public class StatusFilterPredicateTest {
|
|||||||
assertTrue("should pass because matching filter is for hiding",
|
assertTrue("should pass because matching filter is for hiding",
|
||||||
new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic));
|
new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAltTextFilterNoPass() {
|
||||||
|
assertFalse("should not pass because of no alt text",
|
||||||
|
new StatusFilterPredicate(allFilters, HOME).test(noAltText));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAltTextFilterPass() {
|
||||||
|
assertTrue("should pass because of alt text",
|
||||||
|
new StatusFilterPredicate(allFilters, HOME).test(withAltText));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,6 @@ import com.google.gson.reflect.TypeToken;
|
|||||||
|
|
||||||
import org.joinmastodon.android.model.ContentType;
|
import org.joinmastodon.android.model.ContentType;
|
||||||
import org.joinmastodon.android.model.TimelineDefinition;
|
import org.joinmastodon.android.model.TimelineDefinition;
|
||||||
import org.joinmastodon.android.ui.utils.ColorPalette;
|
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -43,6 +42,7 @@ public class GlobalUserPreferences{
|
|||||||
public static boolean disableAltTextReminder;
|
public static boolean disableAltTextReminder;
|
||||||
public static boolean showAltIndicator;
|
public static boolean showAltIndicator;
|
||||||
public static boolean showNoAltIndicator;
|
public static boolean showNoAltIndicator;
|
||||||
|
public static boolean showPostsWithoutAlt;
|
||||||
public static boolean enablePreReleases;
|
public static boolean enablePreReleases;
|
||||||
public static PrefixRepliesMode prefixReplies;
|
public static PrefixRepliesMode prefixReplies;
|
||||||
public static boolean bottomEncoding;
|
public static boolean bottomEncoding;
|
||||||
@@ -129,6 +129,7 @@ public class GlobalUserPreferences{
|
|||||||
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
|
disableAltTextReminder=prefs.getBoolean("disableAltTextReminder", false);
|
||||||
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
showAltIndicator=prefs.getBoolean("showAltIndicator", true);
|
||||||
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
showNoAltIndicator=prefs.getBoolean("showNoAltIndicator", true);
|
||||||
|
showPostsWithoutAlt =prefs.getBoolean("showPostsWithoutAlt", true);
|
||||||
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
|
enablePreReleases=prefs.getBoolean("enablePreReleases", false);
|
||||||
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name()));
|
prefixReplies=PrefixRepliesMode.valueOf(prefs.getString("prefixReplies", PrefixRepliesMode.NEVER.name()));
|
||||||
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
bottomEncoding=prefs.getBoolean("bottomEncoding", false);
|
||||||
@@ -205,6 +206,7 @@ public class GlobalUserPreferences{
|
|||||||
.putBoolean("disableAltTextReminder", disableAltTextReminder)
|
.putBoolean("disableAltTextReminder", disableAltTextReminder)
|
||||||
.putBoolean("showAltIndicator", showAltIndicator)
|
.putBoolean("showAltIndicator", showAltIndicator)
|
||||||
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
.putBoolean("showNoAltIndicator", showNoAltIndicator)
|
||||||
|
.putBoolean("showPostsWithoutAlt", showPostsWithoutAlt)
|
||||||
.putBoolean("enablePreReleases", enablePreReleases)
|
.putBoolean("enablePreReleases", enablePreReleases)
|
||||||
.putString("prefixReplies", prefixReplies.name())
|
.putString("prefixReplies", prefixReplies.name())
|
||||||
.putBoolean("collapseLongPosts", collapseLongPosts)
|
.putBoolean("collapseLongPosts", collapseLongPosts)
|
||||||
|
|||||||
@@ -296,6 +296,10 @@ public class SettingsFragment extends MastodonToolbarFragment implements Provide
|
|||||||
GlobalUserPreferences.showNoAltIndicator=i.checked;
|
GlobalUserPreferences.showNoAltIndicator=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_show_posts_without_alt, R.drawable.ic_fluent_eye_tracking_on_24_regular, GlobalUserPreferences.showPostsWithoutAlt, i->{
|
||||||
|
GlobalUserPreferences.showPostsWithoutAlt =i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_regular, GlobalUserPreferences.collapseLongPosts, i->{
|
items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_regular, GlobalUserPreferences.collapseLongPosts, i->{
|
||||||
GlobalUserPreferences.collapseLongPosts=i.checked;
|
GlobalUserPreferences.collapseLongPosts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
|
|||||||
@@ -64,6 +64,11 @@ public class TimeLineFragment extends SettingsBaseFragment{
|
|||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
needAppRestart=true;
|
needAppRestart=true;
|
||||||
}));
|
}));
|
||||||
|
items.add(new SwitchItem(R.string.sk_settings_show_posts_without_alt, R.drawable.ic_fluent_eye_tracking_on_24_regular, GlobalUserPreferences.showPostsWithoutAlt, i->{
|
||||||
|
GlobalUserPreferences.showPostsWithoutAlt =i.checked;
|
||||||
|
GlobalUserPreferences.save();
|
||||||
|
needAppRestart=true;
|
||||||
|
}));
|
||||||
items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_regular, GlobalUserPreferences.collapseLongPosts, i->{
|
items.add(new SwitchItem(R.string.sk_settings_collapse_long_posts, R.drawable.ic_fluent_chevron_down_24_regular, GlobalUserPreferences.collapseLongPosts, i->{
|
||||||
GlobalUserPreferences.collapseLongPosts=i.checked;
|
GlobalUserPreferences.collapseLongPosts=i.checked;
|
||||||
GlobalUserPreferences.save();
|
GlobalUserPreferences.save();
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.joinmastodon.android.model;
|
||||||
|
|
||||||
|
import org.joinmastodon.android.GlobalUserPreferences;
|
||||||
|
import org.joinmastodon.android.R;
|
||||||
|
import org.jsoup.internal.StringUtil;
|
||||||
|
|
||||||
|
import java.util.EnumSet;
|
||||||
|
|
||||||
|
public class AltTextFilter extends Filter {
|
||||||
|
|
||||||
|
public AltTextFilter(FilterAction filterAction, FilterContext firstContext, FilterContext... restContexts) {
|
||||||
|
this.filterAction = filterAction;
|
||||||
|
isRemote = false;
|
||||||
|
context = EnumSet.of(firstContext, restContexts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean matches(Status status) {
|
||||||
|
return !GlobalUserPreferences.showPostsWithoutAlt && status.getContentStatus().mediaAttachments.stream().map(attachment -> attachment.description).anyMatch(StringUtil::isBlank);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,18 +4,14 @@ import android.content.Context;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.ProgressBar;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import org.joinmastodon.android.R;
|
import org.joinmastodon.android.R;
|
||||||
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
import org.joinmastodon.android.fragments.BaseStatusListFragment;
|
||||||
|
import org.joinmastodon.android.model.AltTextFilter;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
// Mind the gap!
|
// Mind the gap!
|
||||||
@@ -55,7 +51,11 @@ public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{
|
|||||||
@Override
|
@Override
|
||||||
public void onBind(WarningFilteredStatusDisplayItem item) {
|
public void onBind(WarningFilteredStatusDisplayItem item) {
|
||||||
filteredItems = item.filteredItems;
|
filteredItems = item.filteredItems;
|
||||||
text.setText(item.parentFragment.getString(R.string.sk_filtered, item.applyingFilter.title));
|
if(item.applyingFilter instanceof AltTextFilter){
|
||||||
|
text.setText(item.parentFragment.getString(R.string.sk_filtered,item.parentFragment.getString(R.string.sk_no_alt_text)));
|
||||||
|
}else{
|
||||||
|
text.setText(item.parentFragment.getString(R.string.sk_filtered, item.applyingFilter.title));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,15 @@
|
|||||||
package org.joinmastodon.android.utils;
|
package org.joinmastodon.android.utils;
|
||||||
|
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterAction.HIDE;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterAction.WARN;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterContext.ACCOUNT;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterContext.HOME;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterContext.NOTIFICATIONS;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterContext.PUBLIC;
|
||||||
|
import static org.joinmastodon.android.model.Filter.FilterContext.THREAD;
|
||||||
|
|
||||||
import org.joinmastodon.android.api.session.AccountSessionManager;
|
import org.joinmastodon.android.api.session.AccountSessionManager;
|
||||||
|
import org.joinmastodon.android.model.AltTextFilter;
|
||||||
import org.joinmastodon.android.model.Filter;
|
import org.joinmastodon.android.model.Filter;
|
||||||
import org.joinmastodon.android.model.Status;
|
import org.joinmastodon.android.model.Status;
|
||||||
|
|
||||||
@@ -11,7 +20,11 @@ import java.util.function.Predicate;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class StatusFilterPredicate implements Predicate<Status>{
|
public class StatusFilterPredicate implements Predicate<Status> {
|
||||||
|
|
||||||
|
//Hide in timelines and warn in threads
|
||||||
|
private final List<Filter> clientFilters = List.of( new AltTextFilter(HIDE, HOME, PUBLIC, ACCOUNT),
|
||||||
|
new AltTextFilter(WARN, THREAD, NOTIFICATIONS));
|
||||||
private final List<Filter> filters;
|
private final List<Filter> filters;
|
||||||
private final Filter.FilterContext context;
|
private final Filter.FilterContext context;
|
||||||
private final Filter.FilterAction action;
|
private final Filter.FilterAction action;
|
||||||
@@ -19,26 +32,26 @@ public class StatusFilterPredicate implements Predicate<Status>{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param context null makes the predicate pass automatically
|
* @param context null makes the predicate pass automatically
|
||||||
* @param action defines what the predicate should check:
|
* @param action defines what the predicate should check:
|
||||||
* status should not be hidden or should not display with warning
|
* status should not be hidden or should not display with warning
|
||||||
*/
|
*/
|
||||||
public StatusFilterPredicate(List<Filter> filters, Filter.FilterContext context, Filter.FilterAction action){
|
public StatusFilterPredicate(List<Filter> filters, Filter.FilterContext context, Filter.FilterAction action) {
|
||||||
this.filters = filters;
|
this.filters = filters;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StatusFilterPredicate(List<Filter> filters, Filter.FilterContext context){
|
public StatusFilterPredicate(List<Filter> filters, Filter.FilterContext context) {
|
||||||
this(filters, context, Filter.FilterAction.HIDE);
|
this(filters, context, HIDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param context null makes the predicate pass automatically
|
* @param context null makes the predicate pass automatically
|
||||||
* @param action defines what the predicate should check:
|
* @param action defines what the predicate should check:
|
||||||
* status should not be hidden or should not display with warning
|
* status should not be hidden or should not display with warning
|
||||||
*/
|
*/
|
||||||
public StatusFilterPredicate(String accountID, Filter.FilterContext context, Filter.FilterAction action){
|
public StatusFilterPredicate(String accountID, Filter.FilterContext context, Filter.FilterAction action) {
|
||||||
filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(context)).collect(Collectors.toList());
|
filters = AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f -> f.context.contains(context)).collect(Collectors.toList());
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
}
|
}
|
||||||
@@ -46,8 +59,8 @@ public class StatusFilterPredicate implements Predicate<Status>{
|
|||||||
/**
|
/**
|
||||||
* @param context null makes the predicate pass automatically
|
* @param context null makes the predicate pass automatically
|
||||||
*/
|
*/
|
||||||
public StatusFilterPredicate(String accountID, Filter.FilterContext context){
|
public StatusFilterPredicate(String accountID, Filter.FilterContext context) {
|
||||||
this(accountID, context, Filter.FilterAction.HIDE);
|
this(accountID, context, HIDE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -57,7 +70,7 @@ public class StatusFilterPredicate implements Predicate<Status>{
|
|||||||
* false = filter this status
|
* false = filter this status
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public boolean test(Status status){
|
public boolean test(Status status) {
|
||||||
if (context == null) return true;
|
if (context == null) return true;
|
||||||
|
|
||||||
Stream<Filter> matchingFilters = status.filtered != null
|
Stream<Filter> matchingFilters = status.filtered != null
|
||||||
@@ -72,9 +85,21 @@ public class StatusFilterPredicate implements Predicate<Status>{
|
|||||||
// only apply filters for given context
|
// only apply filters for given context
|
||||||
.filter(filter -> filter.context.contains(context))
|
.filter(filter -> filter.context.contains(context))
|
||||||
// treating filterAction = null (from filters list) as FilterAction.HIDE
|
// treating filterAction = null (from filters list) as FilterAction.HIDE
|
||||||
.filter(filter -> filter.filterAction == null ? action == Filter.FilterAction.HIDE : filter.filterAction == action)
|
.filter(filter -> filter.filterAction == null ? action == HIDE : filter.filterAction == action)
|
||||||
.findAny();
|
.findAny();
|
||||||
|
|
||||||
|
//Apply client filters if no server filter is triggered
|
||||||
|
if (applyingFilter.isEmpty()) {
|
||||||
|
applyingFilter = clientFilters.stream()
|
||||||
|
// only apply filters for given context
|
||||||
|
.filter(filter -> filter.context.contains(context))
|
||||||
|
// treating filterAction = null (from filters list) as FilterAction.HIDE
|
||||||
|
.filter(filter -> filter.filterAction == null ? action == HIDE : filter.filterAction == action)
|
||||||
|
//client filter has to match the status
|
||||||
|
.filter(filter -> filter.matches(status))
|
||||||
|
.findAny();
|
||||||
|
}
|
||||||
|
|
||||||
this.applyingFilter = applyingFilter.orElse(null);
|
this.applyingFilter = applyingFilter.orElse(null);
|
||||||
return applyingFilter.isEmpty();
|
return applyingFilter.isEmpty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@drawable/ic_fluent_eye_off_24_regular"/>
|
||||||
|
<item android:drawable="@drawable/ic_fluent_important_24_regular" android:top="14sp" android:width="10sp" android:height="10sp"/>
|
||||||
|
</layer-list>
|
||||||
@@ -231,6 +231,7 @@
|
|||||||
<string name="sk_no_alt_text">Keine Bildbeschreibung</string>
|
<string name="sk_no_alt_text">Keine Bildbeschreibung</string>
|
||||||
<string name="sk_settings_show_alt_indicator">Indikator für Bildbeschreibungen</string>
|
<string name="sk_settings_show_alt_indicator">Indikator für Bildbeschreibungen</string>
|
||||||
<string name="sk_settings_show_no_alt_indicator">Indikator für fehlende Bildbeschreibungen</string>
|
<string name="sk_settings_show_no_alt_indicator">Indikator für fehlende Bildbeschreibungen</string>
|
||||||
|
<string name="sk_settings_show_posts_without_alt">Zeige Medienbeiträge ohne Bildbeschreibung</string>
|
||||||
<string name="sk_updater_enable_pre_releases">Vorab-Versionen einschalten</string>
|
<string name="sk_updater_enable_pre_releases">Vorab-Versionen einschalten</string>
|
||||||
<string name="sk_searching">Wird gesucht…</string>
|
<string name="sk_searching">Wird gesucht…</string>
|
||||||
<string name="sk_save_draft_message">Änderungen speichern oder Entwurf jetzt veröffentlichen\?</string>
|
<string name="sk_save_draft_message">Änderungen speichern oder Entwurf jetzt veröffentlichen\?</string>
|
||||||
|
|||||||
@@ -259,6 +259,7 @@
|
|||||||
<string name="sk_no_alt_text">No alt text available</string>
|
<string name="sk_no_alt_text">No alt text available</string>
|
||||||
<string name="sk_settings_show_alt_indicator">Indicator for alt texts</string>
|
<string name="sk_settings_show_alt_indicator">Indicator for alt texts</string>
|
||||||
<string name="sk_settings_show_no_alt_indicator">Indicator for missing alt texts</string>
|
<string name="sk_settings_show_no_alt_indicator">Indicator for missing alt texts</string>
|
||||||
|
<string name="sk_settings_show_posts_without_alt">Show media posts with missing alt text</string>
|
||||||
<string name="sk_updater_enable_pre_releases">Enable pre-releases</string>
|
<string name="sk_updater_enable_pre_releases">Enable pre-releases</string>
|
||||||
<string name="sk_settings_show_new_posts_button">“Show new posts” button</string>
|
<string name="sk_settings_show_new_posts_button">“Show new posts” button</string>
|
||||||
<string name="sk_inline_local_only">local-only</string>
|
<string name="sk_inline_local_only">local-only</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user