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 2a2948b63..0d1530a73 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 422fe6fde..afb124604 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 c2d282232..0739f9055 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 93a9db135..74a8d76c2 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