diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/PreferencesFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/PreferencesFragment.java
index 2a2948b6..0d1530a7 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/PreferencesFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/PreferencesFragment.java
@@ -15,6 +15,7 @@
package com.keylesspalace.tusky.fragment;
+import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
@@ -24,6 +25,8 @@ import android.preference.EditTextPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.support.annotation.XmlRes;
+import android.text.Editable;
+import android.text.TextWatcher;
import com.keylesspalace.tusky.BuildConfig;
import com.keylesspalace.tusky.PreferencesActivity;
@@ -32,6 +35,8 @@ import com.keylesspalace.tusky.TuskyApplication;
import com.keylesspalace.tusky.db.AccountEntity;
import com.keylesspalace.tusky.db.AccountManager;
+import java.util.regex.Pattern;
+
public class PreferencesFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
SharedPreferences sharedPreferences;
static boolean httpProxyChanged = false;
@@ -62,6 +67,32 @@ public class PreferencesFragment extends PreferenceFragment implements SharedPre
addPreferencesFromResource(preference);
+ Preference regexPref = findPreference("tabFilterRegex");
+ if (regexPref != null) regexPref.setOnPreferenceClickListener(pref -> {
+ // Reset the error dialog when shown; if the dialog was closed with the cancel button
+ // while an invalid regex was present, this would otherwise cause buggy behaviour.
+ ((EditTextPreference) regexPref).getEditText().setError(null);
+
+ // Test the regex as the user inputs text, ensuring immediate feedback and preventing
+ // setting of an invalid regex, which would cause a crash loop.
+ ((EditTextPreference) regexPref).getEditText().addTextChangedListener(new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable s) {
+ try {
+ Pattern.compile(s.toString());
+ ((EditTextPreference) regexPref).getEditText().setError(null);
+ AlertDialog dialog = (AlertDialog) ((EditTextPreference) pref).getDialog();
+ if (dialog != null) dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
+ } catch (IllegalArgumentException e) {
+ ((AlertDialog) ((EditTextPreference) pref).getDialog()).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+ ((EditTextPreference) regexPref).getEditText().setError(getString(R.string.error_invalid_regex));
+ }
+ }
+ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+ @Override public void onTextChanged(CharSequence s, int start, int before, int count) {}
+ });
+ return false;
+ });
Preference notificationPreferences = findPreference("notificationPreferences");
diff --git a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
index 422fe6fd..afb12460 100644
--- a/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java
@@ -61,6 +61,8 @@ import com.keylesspalace.tusky.viewdata.StatusViewData;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.inject.Inject;
@@ -110,6 +112,8 @@ public class TimelineFragment extends SFragment implements
private TabLayout.OnTabSelectedListener onTabSelectedListener;
private boolean filterRemoveReplies;
private boolean filterRemoveReblogs;
+ private boolean filterRemoveRegex;
+ private Matcher filterRemoveRegexMatcher;
private boolean hideFab;
private TimelineReceiver timelineReceiver;
private boolean topLoading;
@@ -212,6 +216,10 @@ public class TimelineFragment extends SFragment implements
filter = preferences.getBoolean("tabFilterHomeBoosts", true);
filterRemoveReblogs = kind == Kind.HOME && !filter;
+ String regexFilter = preferences.getString("tabFilterRegex", "");
+ filterRemoveRegex = (kind == Kind.HOME || kind == Kind.PUBLIC_LOCAL || kind == Kind.PUBLIC_FEDERATED) && !regexFilter.isEmpty();
+ if (filterRemoveRegex) filterRemoveRegexMatcher = Pattern.compile(regexFilter, Pattern.CASE_INSENSITIVE).matcher("");
+
timelineReceiver = new TimelineReceiver(this, this);
LocalBroadcastManager.getInstance(context.getApplicationContext())
.registerReceiver(timelineReceiver, TimelineReceiver.getFilter(kind));
@@ -497,6 +505,22 @@ public class TimelineFragment extends SFragment implements
}
break;
}
+ case "tabFilterRegex": {
+ boolean oldFilterRemoveRegex = filterRemoveRegex;
+ String newFilterRemoveRegexPattern = sharedPreferences.getString("tabFilterRegex", "");
+ boolean patternChanged;
+ if (filterRemoveRegexMatcher != null) {
+ patternChanged = !newFilterRemoveRegexPattern.equalsIgnoreCase(filterRemoveRegexMatcher.pattern().pattern());
+ } else {
+ patternChanged = !newFilterRemoveRegexPattern.isEmpty();
+ }
+ filterRemoveRegex = (kind == Kind.HOME || kind == Kind.PUBLIC_LOCAL || kind == Kind.PUBLIC_FEDERATED) && !newFilterRemoveRegexPattern.isEmpty();
+ if (oldFilterRemoveRegex != filterRemoveRegex || patternChanged) {
+ filterRemoveRegexMatcher = Pattern.compile(newFilterRemoveRegexPattern, Pattern.CASE_INSENSITIVE).matcher("");
+ fullyRefresh();
+ }
+ break;
+ }
case "alwaysShowSensitiveMedia": {
//it is ok if only newly loaded statuses are affected, no need to fully refresh
alwaysShowSensitiveMedia = sharedPreferences.getBoolean("alwaysShowSensitiveMedia", false);
@@ -701,7 +725,9 @@ public class TimelineFragment extends SFragment implements
while (it.hasNext()) {
Status status = it.next();
if ((status.getInReplyToId() != null && filterRemoveReplies)
- || (status.getReblog() != null && filterRemoveReblogs)) {
+ || (status.getReblog() != null && filterRemoveReblogs)
+ || (filterRemoveRegex && (filterRemoveRegexMatcher.reset(status.getContent()).find()
+ || (!status.getSpoilerText().isEmpty() && filterRemoveRegexMatcher.reset(status.getContent()).find())))) {
it.remove();
}
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c2d28223..0739f905 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -17,8 +17,10 @@
Images and videos cannot both be attached to the same status.
The upload failed.
At least one status must be reported.
+ Invalid regular expression
Home
+ Advanced
Notifications
Local
Federated
@@ -177,6 +179,7 @@
Tabs
Show boosts
Show replies
+ Filter out by regular expressions
Show media previews
Proxy
HTTP proxy
diff --git a/app/src/main/res/xml/timeline_filter_preferences.xml b/app/src/main/res/xml/timeline_filter_preferences.xml
index 93a9db13..74a8d76c 100644
--- a/app/src/main/res/xml/timeline_filter_preferences.xml
+++ b/app/src/main/res/xml/timeline_filter_preferences.xml
@@ -13,4 +13,11 @@
android:title="@string/pref_title_show_replies" />
+
+
+
+
\ No newline at end of file