|
|
|
@ -26,7 +26,9 @@ import android.net.Uri; |
|
|
|
|
import android.os.Environment; |
|
|
|
|
import android.text.SpannableStringBuilder; |
|
|
|
|
import android.text.Spanned; |
|
|
|
|
import android.text.TextUtils; |
|
|
|
|
import android.text.style.URLSpan; |
|
|
|
|
import android.util.Log; |
|
|
|
|
import android.view.Menu; |
|
|
|
|
import android.view.MenuItem; |
|
|
|
|
import android.view.View; |
|
|
|
@ -51,17 +53,25 @@ import com.keylesspalace.tusky.db.AccountEntity; |
|
|
|
|
import com.keylesspalace.tusky.db.AccountManager; |
|
|
|
|
import com.keylesspalace.tusky.di.Injectable; |
|
|
|
|
import com.keylesspalace.tusky.entity.Attachment; |
|
|
|
|
import com.keylesspalace.tusky.entity.Filter; |
|
|
|
|
import com.keylesspalace.tusky.entity.Status; |
|
|
|
|
import com.keylesspalace.tusky.network.MastodonApi; |
|
|
|
|
import com.keylesspalace.tusky.network.TimelineCases; |
|
|
|
|
import com.keylesspalace.tusky.viewdata.AttachmentViewData; |
|
|
|
|
|
|
|
|
|
import java.util.ArrayList; |
|
|
|
|
import java.util.LinkedHashSet; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.Set; |
|
|
|
|
import java.util.regex.Matcher; |
|
|
|
|
import java.util.regex.Pattern; |
|
|
|
|
|
|
|
|
|
import javax.inject.Inject; |
|
|
|
|
|
|
|
|
|
import retrofit2.Call; |
|
|
|
|
import retrofit2.Callback; |
|
|
|
|
import retrofit2.Response; |
|
|
|
|
|
|
|
|
|
/* Note from Andrew on Jan. 22, 2017: This class is a design problem for me, so I left it with an |
|
|
|
|
* awkward name. TimelineFragment and NotificationFragment have significant overlap but the nature |
|
|
|
|
* of that is complicated by how they're coupled with Status and Notification and the corresponding |
|
|
|
@ -76,6 +86,10 @@ public abstract class SFragment extends BaseFragment implements Injectable { |
|
|
|
|
|
|
|
|
|
private BottomSheetActivity bottomSheetActivity; |
|
|
|
|
|
|
|
|
|
private static List<Filter> filters; |
|
|
|
|
private boolean filterRemoveRegex; |
|
|
|
|
private Matcher filterRemoveRegexMatcher; |
|
|
|
|
|
|
|
|
|
@Inject |
|
|
|
|
public MastodonApi mastodonApi; |
|
|
|
|
@Inject |
|
|
|
@ -83,6 +97,8 @@ public abstract class SFragment extends BaseFragment implements Injectable { |
|
|
|
|
@Inject |
|
|
|
|
public TimelineCases timelineCases; |
|
|
|
|
|
|
|
|
|
private static final String TAG = "SFragment"; |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void startActivity(Intent intent) { |
|
|
|
|
super.startActivity(intent); |
|
|
|
@ -418,4 +434,69 @@ public abstract class SFragment extends BaseFragment implements Injectable { |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void reloadFilters(boolean forceRefresh) { |
|
|
|
|
if (filters != null && !forceRefresh) { |
|
|
|
|
applyFilters(forceRefresh); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
mastodonApi.getFilters().enqueue(new Callback<List<Filter>>() { |
|
|
|
|
@Override |
|
|
|
|
public void onResponse(@NonNull Call<List<Filter>> call, @NonNull Response<List<Filter>> response) { |
|
|
|
|
filters = response.body(); |
|
|
|
|
if (response.isSuccessful() && filters != null) { |
|
|
|
|
applyFilters(forceRefresh); |
|
|
|
|
} else { |
|
|
|
|
Log.e(TAG, "Error getting filters from server"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public void onFailure(@NonNull Call<List<Filter>> call, @NonNull Throwable t) { |
|
|
|
|
Log.e(TAG, "Error getting filters from server", t); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected boolean filterIsRelevant(Filter filter) { |
|
|
|
|
// Called when building local filter expression
|
|
|
|
|
// Override to select relevant filters for your fragment
|
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected void refreshAfterApplyingFilters() { |
|
|
|
|
// Called after filters are updated
|
|
|
|
|
// Override to refresh your fragment
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
boolean shouldFilterStatus(Status status) { |
|
|
|
|
return (filterRemoveRegex && (filterRemoveRegexMatcher.reset(status.getActionableStatus().getContent()).find() |
|
|
|
|
|| (!status.getSpoilerText().isEmpty() && filterRemoveRegexMatcher.reset(status.getActionableStatus().getSpoilerText()).find()))); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void applyFilters(boolean refresh) { |
|
|
|
|
List<String> tokens = new ArrayList<>(); |
|
|
|
|
for (Filter filter : filters) { |
|
|
|
|
if (filterIsRelevant(filter)) { |
|
|
|
|
tokens.add(filterToRegexToken(filter)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
filterRemoveRegex = !tokens.isEmpty(); |
|
|
|
|
if (filterRemoveRegex) { |
|
|
|
|
filterRemoveRegexMatcher = Pattern.compile(TextUtils.join("|", tokens), Pattern.CASE_INSENSITIVE).matcher(""); |
|
|
|
|
} |
|
|
|
|
if (refresh) { |
|
|
|
|
refreshAfterApplyingFilters(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static String filterToRegexToken(Filter filter) { |
|
|
|
|
String phrase = Pattern.quote(filter.getPhrase()); |
|
|
|
|
return filter.getWholeWord() ? String.format("(^|\\W)%s($|\\W)", phrase) : phrase; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public static void flushFilters() { |
|
|
|
|
filters = null; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|