mirror of
https://github.com/TwidereProject/Twidere-Android
synced 2025-02-14 18:50:39 +01:00
improved filters
This commit is contained in:
parent
24e5781b8c
commit
1cd8580f45
@ -116,6 +116,7 @@ import org.mariotaku.twidere.receiver.NotificationReceiver;
|
|||||||
import org.mariotaku.twidere.service.BackgroundOperationService;
|
import org.mariotaku.twidere.service.BackgroundOperationService;
|
||||||
import org.mariotaku.twidere.util.ActivityTracker;
|
import org.mariotaku.twidere.util.ActivityTracker;
|
||||||
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
|
||||||
|
import org.mariotaku.twidere.util.DataStoreFunctionsKt;
|
||||||
import org.mariotaku.twidere.util.DataStoreUtils;
|
import org.mariotaku.twidere.util.DataStoreUtils;
|
||||||
import org.mariotaku.twidere.util.ImagePreloader;
|
import org.mariotaku.twidere.util.ImagePreloader;
|
||||||
import org.mariotaku.twidere.util.InternalTwitterContentUtils;
|
import org.mariotaku.twidere.util.InternalTwitterContentUtils;
|
||||||
@ -1249,7 +1250,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta
|
|||||||
final NotificationManagerWrapper nm = mNotificationManager;
|
final NotificationManagerWrapper nm = mNotificationManager;
|
||||||
final Expression selection = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
final Expression selection = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
||||||
Expression.greaterThan(Statuses.POSITION_KEY, position));
|
Expression.greaterThan(Statuses.POSITION_KEY, position));
|
||||||
final String filteredSelection = DataStoreUtils.buildStatusFilterWhereClause(preferences,
|
final String filteredSelection = DataStoreFunctionsKt.buildStatusFilterWhereClause(preferences,
|
||||||
Statuses.TABLE_NAME, selection).getSQL();
|
Statuses.TABLE_NAME, selection).getSQL();
|
||||||
final String[] selectionArgs = {accountKey.toString()};
|
final String[] selectionArgs = {accountKey.toString()};
|
||||||
final String[] userProjection = {Statuses.USER_KEY, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME};
|
final String[] userProjection = {Statuses.USER_KEY, Statuses.USER_NAME, Statuses.USER_SCREEN_NAME};
|
||||||
|
@ -42,7 +42,6 @@ import com.bluelinelabs.logansquare.LoganSquare;
|
|||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.mariotaku.kpreferences.SharedPreferencesExtensionsKt;
|
|
||||||
import org.mariotaku.microblog.library.twitter.model.Activity;
|
import org.mariotaku.microblog.library.twitter.model.Activity;
|
||||||
import org.mariotaku.sqliteqb.library.ArgsArray;
|
import org.mariotaku.sqliteqb.library.ArgsArray;
|
||||||
import org.mariotaku.sqliteqb.library.Columns;
|
import org.mariotaku.sqliteqb.library.Columns;
|
||||||
@ -56,7 +55,6 @@ import org.mariotaku.sqliteqb.library.Tables;
|
|||||||
import org.mariotaku.sqliteqb.library.query.SQLSelectQuery;
|
import org.mariotaku.sqliteqb.library.query.SQLSelectQuery;
|
||||||
import org.mariotaku.twidere.Constants;
|
import org.mariotaku.twidere.Constants;
|
||||||
import org.mariotaku.twidere.TwidereConstants;
|
import org.mariotaku.twidere.TwidereConstants;
|
||||||
import org.mariotaku.twidere.constant.PreferenceKeysKt;
|
|
||||||
import org.mariotaku.twidere.extension.AccountExtensionsKt;
|
import org.mariotaku.twidere.extension.AccountExtensionsKt;
|
||||||
import org.mariotaku.twidere.model.FiltersData;
|
import org.mariotaku.twidere.model.FiltersData;
|
||||||
import org.mariotaku.twidere.model.FiltersData$BaseItemValuesCreator;
|
import org.mariotaku.twidere.model.FiltersData$BaseItemValuesCreator;
|
||||||
@ -325,69 +323,6 @@ public class DataStoreUtils implements Constants {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Expression buildStatusFilterWhereClause(@NonNull final SharedPreferences preferences,
|
|
||||||
@NonNull final String table,
|
|
||||||
@Nullable final Expression extraSelection) {
|
|
||||||
final SQLSelectQuery filteredUsersQuery = SQLQueryBuilder
|
|
||||||
.select(new Column(new Table(Filters.Users.TABLE_NAME), Filters.Users.USER_KEY))
|
|
||||||
.from(new Tables(Filters.Users.TABLE_NAME))
|
|
||||||
.build();
|
|
||||||
final Expression filteredUsersWhere = Expression.or(
|
|
||||||
Expression.in(new Column(new Table(table), Statuses.USER_KEY), filteredUsersQuery),
|
|
||||||
Expression.in(new Column(new Table(table), Statuses.RETWEETED_BY_USER_KEY), filteredUsersQuery),
|
|
||||||
Expression.in(new Column(new Table(table), Statuses.QUOTED_USER_KEY), filteredUsersQuery)
|
|
||||||
);
|
|
||||||
final SQLSelectQuery.Builder filteredIdsQueryBuilder = SQLQueryBuilder
|
|
||||||
.select(new Column(new Table(table), Statuses._ID))
|
|
||||||
.from(new Tables(table))
|
|
||||||
.where(filteredUsersWhere)
|
|
||||||
.union()
|
|
||||||
.select(new Columns(new Column(new Table(table), Statuses._ID)))
|
|
||||||
.from(new Tables(table, Filters.Sources.TABLE_NAME))
|
|
||||||
.where(Expression.or(
|
|
||||||
Expression.likeRaw(new Column(new Table(table), Statuses.SOURCE),
|
|
||||||
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'"),
|
|
||||||
Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_SOURCE),
|
|
||||||
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'")
|
|
||||||
))
|
|
||||||
.union()
|
|
||||||
.select(new Columns(new Column(new Table(table), Statuses._ID)))
|
|
||||||
.from(new Tables(table, Filters.Keywords.TABLE_NAME))
|
|
||||||
.where(Expression.or(
|
|
||||||
Expression.likeRaw(new Column(new Table(table), Statuses.TEXT_PLAIN),
|
|
||||||
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"),
|
|
||||||
Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_TEXT_PLAIN),
|
|
||||||
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'")
|
|
||||||
))
|
|
||||||
.union()
|
|
||||||
.select(new Columns(new Column(new Table(table), Statuses._ID)))
|
|
||||||
.from(new Tables(table, Filters.Links.TABLE_NAME))
|
|
||||||
.where(Expression.or(
|
|
||||||
Expression.likeRaw(new Column(new Table(table), Statuses.SPANS),
|
|
||||||
"'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'"),
|
|
||||||
Expression.likeRaw(new Column(new Table(table), Statuses.QUOTED_SPANS),
|
|
||||||
"'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'")
|
|
||||||
));
|
|
||||||
int filterFlags = 0;
|
|
||||||
if (SharedPreferencesExtensionsKt.get(preferences, PreferenceKeysKt.getFilterUnavailableQuoteStatusesKey())) {
|
|
||||||
filterFlags |= ParcelableStatus.FilterFlags.QUOTE_NOT_AVAILABLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Expression filterExpression = Expression.or(
|
|
||||||
Expression.and(
|
|
||||||
new Expression("(" + Statuses.FILTER_FLAGS + " & " + filterFlags + ") == 0"),
|
|
||||||
Expression.notIn(new Column(new Table(table), Statuses._ID), filteredIdsQueryBuilder.build())
|
|
||||||
),
|
|
||||||
Expression.equals(new Column(new Table(table), Statuses.IS_GAP), 1)
|
|
||||||
);
|
|
||||||
if (extraSelection != null) {
|
|
||||||
return Expression.and(filterExpression, extraSelection);
|
|
||||||
}
|
|
||||||
return filterExpression;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static String getAccountDisplayName(final Context context, final UserKey accountKey, final boolean nameFirst) {
|
public static String getAccountDisplayName(final Context context, final UserKey accountKey, final boolean nameFirst) {
|
||||||
final String name;
|
final String name;
|
||||||
if (nameFirst) {
|
if (nameFirst) {
|
||||||
@ -458,7 +393,7 @@ public class DataStoreUtils implements Constants {
|
|||||||
}
|
}
|
||||||
expressionArgs.add(String.valueOf(compare));
|
expressionArgs.add(String.valueOf(compare));
|
||||||
|
|
||||||
expressions.add(buildStatusFilterWhereClause(preferences, getTableNameByUri(uri), null));
|
expressions.add(DataStoreFunctionsKt.buildStatusFilterWhereClause(preferences, getTableNameByUri(uri), null));
|
||||||
|
|
||||||
if (extraArgs != null) {
|
if (extraArgs != null) {
|
||||||
Parcelable extras = extraArgs.getParcelable(EXTRA_EXTRAS);
|
Parcelable extras = extraArgs.getParcelable(EXTRA_EXTRAS);
|
||||||
|
@ -77,18 +77,6 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
|||||||
showProgress()
|
showProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override
|
|
||||||
// public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
|
|
||||||
// final View view = super.onCreateView(inflater, container, savedInstanceState);
|
|
||||||
// assert view != null;
|
|
||||||
// final ListView listView = (ListView) view.findViewById(R.id.list_view);
|
|
||||||
// final Resources res = getResources();
|
|
||||||
// final float density = res.getDisplayMetrics().density;
|
|
||||||
// final int padding = (int) density * 16;
|
|
||||||
// listView.setPadding(padding, 0, padding, 0);
|
|
||||||
// return view;
|
|
||||||
// }
|
|
||||||
|
|
||||||
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
|
||||||
super.setUserVisibleHint(isVisibleToUser)
|
super.setUserVisibleHint(isVisibleToUser)
|
||||||
if (!isVisibleToUser && actionMode != null) {
|
if (!isVisibleToUser && actionMode != null) {
|
||||||
@ -170,7 +158,7 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFinished(loader: Loader<Cursor?>, data: Cursor?) {
|
override fun onLoadFinished(loader: Loader<Cursor?>, data: Cursor?) {
|
||||||
adapter!!.swapCursor(data)
|
adapter.swapCursor(data)
|
||||||
if (data != null && data.count > 0) {
|
if (data != null && data.count > 0) {
|
||||||
showContent()
|
showContent()
|
||||||
} else {
|
} else {
|
||||||
@ -179,14 +167,13 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoaderReset(loader: Loader<Cursor?>) {
|
override fun onLoaderReset(loader: Loader<Cursor?>) {
|
||||||
adapter!!.swapCursor(null)
|
adapter.swapCursor(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) {
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
inflater!!.inflate(R.menu.menu_filters, menu)
|
inflater.inflate(R.menu.menu_filters, menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.add -> {
|
R.id.add -> {
|
||||||
@ -338,6 +325,11 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
|||||||
|
|
||||||
class FilteredUsersFragment : BaseFiltersFragment() {
|
class FilteredUsersFragment : BaseFiltersFragment() {
|
||||||
|
|
||||||
|
public override val contentColumns: Array<String>
|
||||||
|
get() = Filters.Users.COLUMNS
|
||||||
|
|
||||||
|
override val contentUri: Uri
|
||||||
|
get() = Filters.Users.CONTENT_URI
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
@ -355,11 +347,9 @@ abstract class BaseFiltersFragment : AbsContentListViewFragment<SimpleCursorAdap
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override val contentColumns: Array<String>
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
get() = Filters.Users.COLUMNS
|
inflater.inflate(R.menu.menu_filters_users, menu)
|
||||||
|
}
|
||||||
override val contentUri: Uri
|
|
||||||
get() = Filters.Users.CONTENT_URI
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
|
@ -47,6 +47,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
|||||||
import org.mariotaku.twidere.util.DataStoreUtils
|
import org.mariotaku.twidere.util.DataStoreUtils
|
||||||
import org.mariotaku.twidere.util.ErrorInfoStore
|
import org.mariotaku.twidere.util.ErrorInfoStore
|
||||||
import org.mariotaku.twidere.util.Utils
|
import org.mariotaku.twidere.util.Utils
|
||||||
|
import org.mariotaku.twidere.util.buildStatusFilterWhereClause
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by mariotaku on 14/12/3.
|
* Created by mariotaku on 14/12/3.
|
||||||
@ -222,7 +223,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() {
|
|||||||
|
|
||||||
protected fun getFiltersWhere(table: String): Expression? {
|
protected fun getFiltersWhere(table: String): Expression? {
|
||||||
if (!isFilterEnabled) return null
|
if (!isFilterEnabled) return null
|
||||||
return DataStoreUtils.buildStatusFilterWhereClause(preferences, table, null)
|
return buildStatusFilterWhereClause(preferences, table, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun getNewestStatusIds(accountKeys: Array<UserKey>): Array<String?>? {
|
protected fun getNewestStatusIds(accountKeys: Array<UserKey>): Array<String?>? {
|
||||||
|
@ -0,0 +1,74 @@
|
|||||||
|
package org.mariotaku.twidere.util
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import org.mariotaku.kpreferences.get
|
||||||
|
import org.mariotaku.sqliteqb.library.*
|
||||||
|
import org.mariotaku.twidere.constant.filterUnavailableQuoteStatusesKey
|
||||||
|
import org.mariotaku.twidere.model.ParcelableStatus.FilterFlags
|
||||||
|
import org.mariotaku.twidere.provider.TwidereDataStore.Filters
|
||||||
|
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Created by mariotaku on 2016/12/24.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun buildStatusFilterWhereClause(preferences: SharedPreferences,
|
||||||
|
table: String,
|
||||||
|
extraSelection: Expression?): Expression {
|
||||||
|
val filteredUsersQuery = SQLQueryBuilder
|
||||||
|
.select(Columns.Column(Table(Filters.Users.TABLE_NAME), Filters.Users.USER_KEY))
|
||||||
|
.from(Tables(Filters.Users.TABLE_NAME))
|
||||||
|
.build()
|
||||||
|
val filteredUsersWhere = Expression.or(
|
||||||
|
Expression.`in`(Columns.Column(Table(table), Statuses.USER_KEY), filteredUsersQuery),
|
||||||
|
Expression.`in`(Columns.Column(Table(table), Statuses.RETWEETED_BY_USER_KEY), filteredUsersQuery),
|
||||||
|
Expression.`in`(Columns.Column(Table(table), Statuses.QUOTED_USER_KEY), filteredUsersQuery)
|
||||||
|
)
|
||||||
|
val filteredIdsQueryBuilder = SQLQueryBuilder
|
||||||
|
.select(Columns.Column(Table(table), Statuses._ID))
|
||||||
|
.from(Tables(table))
|
||||||
|
.where(filteredUsersWhere)
|
||||||
|
.union()
|
||||||
|
.select(Columns(Columns.Column(Table(table), Statuses._ID)))
|
||||||
|
.from(Tables(table, Filters.Sources.TABLE_NAME))
|
||||||
|
.where(Expression.or(
|
||||||
|
Expression.likeRaw(Columns.Column(Table(table), Statuses.SOURCE),
|
||||||
|
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'"),
|
||||||
|
Expression.likeRaw(Columns.Column(Table(table), Statuses.QUOTED_SOURCE),
|
||||||
|
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'")
|
||||||
|
))
|
||||||
|
.union()
|
||||||
|
.select(Columns(Columns.Column(Table(table), Statuses._ID)))
|
||||||
|
.from(Tables(table, Filters.Keywords.TABLE_NAME))
|
||||||
|
.where(Expression.or(
|
||||||
|
Expression.likeRaw(Columns.Column(Table(table), Statuses.TEXT_PLAIN),
|
||||||
|
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"),
|
||||||
|
Expression.likeRaw(Columns.Column(Table(table), Statuses.QUOTED_TEXT_PLAIN),
|
||||||
|
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'")
|
||||||
|
))
|
||||||
|
.union()
|
||||||
|
.select(Columns(Columns.Column(Table(table), Statuses._ID)))
|
||||||
|
.from(Tables(table, Filters.Links.TABLE_NAME))
|
||||||
|
.where(Expression.or(
|
||||||
|
Expression.likeRaw(Columns.Column(Table(table), Statuses.SPANS),
|
||||||
|
"'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'"),
|
||||||
|
Expression.likeRaw(Columns.Column(Table(table), Statuses.QUOTED_SPANS),
|
||||||
|
"'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'")
|
||||||
|
))
|
||||||
|
var filterFlags: Long = 0
|
||||||
|
if (preferences[filterUnavailableQuoteStatusesKey]) {
|
||||||
|
filterFlags = filterFlags or FilterFlags.QUOTE_NOT_AVAILABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
val filterExpression = Expression.or(
|
||||||
|
Expression.and(
|
||||||
|
Expression("(" + Statuses.FILTER_FLAGS + " & " + filterFlags + ") == 0"),
|
||||||
|
Expression.notIn(Columns.Column(Table(table), Statuses._ID), filteredIdsQueryBuilder.build())
|
||||||
|
),
|
||||||
|
Expression.equals(Columns.Column(Table(table), Statuses.IS_GAP), 1)
|
||||||
|
)
|
||||||
|
if (extraSelection != null) {
|
||||||
|
return Expression.and(filterExpression, extraSelection)
|
||||||
|
}
|
||||||
|
return filterExpression
|
||||||
|
}
|
28
twidere/src/main/res/menu/menu_filters_users.xml
Normal file
28
twidere/src/main/res/menu/menu_filters_users.xml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/add_submenu"
|
||||||
|
android:icon="@drawable/ic_action_add"
|
||||||
|
android:title="@string/add"
|
||||||
|
app:showAsAction="always">
|
||||||
|
<menu>
|
||||||
|
<item
|
||||||
|
android:id="@id/add"
|
||||||
|
android:title="@string/select_user"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/import_from_blocked_users"
|
||||||
|
android:enabled="@bool/is_debug"
|
||||||
|
android:title="@string/action_filter_import_from_blocked_users"
|
||||||
|
android:visible="@bool/is_debug"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/import_from_muted_users"
|
||||||
|
android:enabled="@bool/is_debug"
|
||||||
|
android:title="@string/action_filter_import_from_muted_users"
|
||||||
|
android:visible="@bool/is_debug"/>
|
||||||
|
</menu>
|
||||||
|
|
||||||
|
</item>
|
||||||
|
|
||||||
|
</menu>
|
@ -828,4 +828,6 @@
|
|||||||
<string name="status_format_time_source"><xliff:g example="0:00, Jan 1 2017" id="time">%1$s</xliff:g> · <xliff:g example="Twidere for Android" id="source">%2$s</xliff:g></string>
|
<string name="status_format_time_source"><xliff:g example="0:00, Jan 1 2017" id="time">%1$s</xliff:g> · <xliff:g example="Twidere for Android" id="source">%2$s</xliff:g></string>
|
||||||
<string name="status_format_source"><xliff:g example="Twidere for Android" id="source">%s</xliff:g></string>
|
<string name="status_format_source"><xliff:g example="Twidere for Android" id="source">%s</xliff:g></string>
|
||||||
<string name="preference_filter_unavailable_quote_statuses">Filter unavailable quotes</string>
|
<string name="preference_filter_unavailable_quote_statuses">Filter unavailable quotes</string>
|
||||||
|
<string name="action_filter_import_from_blocked_users">Import from blocked users</string>
|
||||||
|
<string name="action_filter_import_from_muted_users">Import from muted users</string>
|
||||||
</resources>
|
</resources>
|
Loading…
x
Reference in New Issue
Block a user