generalize filtering logic
This commit is contained in:
parent
220cd35d82
commit
99f0817bdb
|
@ -74,10 +74,8 @@ public class CacheController{
|
|||
int flags=cursor.getInt(1);
|
||||
status.hasGapAfter=((flags & POST_FLAG_GAP_AFTER)!=0);
|
||||
newMaxID=status.id;
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(status))
|
||||
continue outer;
|
||||
}
|
||||
if (!new StatusFilterPredicate(filters, Filter.FilterContext.HOME).test(status))
|
||||
continue outer;
|
||||
result.add(status);
|
||||
}while(cursor.moveToNext());
|
||||
String _newMaxID=newMaxID;
|
||||
|
@ -92,7 +90,7 @@ public class CacheController{
|
|||
.setCallback(new Callback<>(){
|
||||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(new StatusFilterPredicate(filters, Filter.FilterContext.HOME)).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||
putHomeTimeline(result, maxID==null);
|
||||
}
|
||||
|
||||
|
@ -148,10 +146,8 @@ public class CacheController{
|
|||
ntf.postprocess();
|
||||
newMaxID=ntf.id;
|
||||
if(ntf.status!=null){
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(ntf.status))
|
||||
continue outer;
|
||||
}
|
||||
if (!new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status))
|
||||
continue outer;
|
||||
}
|
||||
result.add(ntf);
|
||||
}while(cursor.moveToNext());
|
||||
|
@ -170,11 +166,7 @@ public class CacheController{
|
|||
public void onSuccess(List<Notification> result){
|
||||
callback.onSuccess(new CacheablePaginatedResponse<>(result.stream().filter(ntf->{
|
||||
if(ntf.status!=null){
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(ntf.status)){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return new StatusFilterPredicate(filters, Filter.FilterContext.NOTIFICATIONS).test(ntf.status);
|
||||
}
|
||||
return true;
|
||||
}).collect(Collectors.toList()), result.isEmpty() ? null : result.get(result.size()-1).id, false));
|
||||
|
|
|
@ -60,7 +60,12 @@ public class AccountTimelineFragment extends StatusListFragment{
|
|||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if(getActivity()==null) return;
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.ACCOUNT)).collect(Collectors.toList());
|
||||
AccountSessionManager asm = AccountSessionManager.getInstance();
|
||||
result=result.stream().filter(status -> {
|
||||
// don't hide own posts in own profile
|
||||
if (asm.isSelf(accountID, user) && asm.isSelf(accountID, status.account)) return true;
|
||||
else return new StatusFilterPredicate(accountID, getFilterContext()).test(status);
|
||||
}).collect(Collectors.toList());
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
|
|
|
@ -123,7 +123,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if (getActivity() == null) return;
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
|
@ -154,4 +154,9 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment {
|
|||
protected void onSetFabBottomInset(int inset){
|
||||
((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -148,7 +148,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||
result.get(result.size()-1).hasGapAfter=true;
|
||||
toAdd=result;
|
||||
}
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
||||
toAdd=toAdd.stream().filter(filterPredicate).collect(Collectors.toList());
|
||||
if(!toAdd.isEmpty()){
|
||||
prependItems(toAdd, true);
|
||||
|
@ -227,7 +227,7 @@ public class HomeTimelineFragment extends StatusListFragment {
|
|||
List<StatusDisplayItem> targetList=displayItems.subList(gapPos, gapPos+1);
|
||||
targetList.clear();
|
||||
List<Status> insertedPosts=data.subList(gapPostIndex+1, gapPostIndex+1);
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, Filter.FilterContext.HOME);
|
||||
StatusFilterPredicate filterPredicate=new StatusFilterPredicate(accountID, getFilterContext());
|
||||
for(Status s:result){
|
||||
if(idsBelowGap.contains(s.id))
|
||||
break;
|
||||
|
|
|
@ -137,7 +137,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment {
|
|||
@Override
|
||||
public void onSuccess(List<Status> result) {
|
||||
if (getActivity() == null) return;
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.HOME)).collect(Collectors.toList());
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
|
|
|
@ -79,7 +79,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment<Schedule
|
|||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(ScheduledStatus s) {
|
||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true);
|
||||
return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.view.View;
|
|||
|
||||
import org.joinmastodon.android.R;
|
||||
import org.joinmastodon.android.api.requests.statuses.GetStatusEditHistory;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.ReblogOrReplyLineStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.StatusDisplayItem;
|
||||
|
@ -55,7 +56,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||
|
||||
@Override
|
||||
protected List<StatusDisplayItem> buildDisplayItems(Status s){
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null);
|
||||
List<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, null);
|
||||
int idx=data.indexOf(s);
|
||||
if(idx>=0){
|
||||
String date=UiUtils.DATE_TIME_FORMATTER.format(s.createdAt.atZone(ZoneId.systemDefault()));
|
||||
|
@ -156,4 +157,9 @@ public class StatusEditHistoryFragment extends StatusListFragment{
|
|||
public boolean isItemEnabled(String id){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
|
|||
return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, getFilterContext());
|
||||
}
|
||||
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
protected abstract Filter.FilterContext getFilterContext();
|
||||
|
||||
@Override
|
||||
protected void addAccountToKnown(Status s){
|
||||
|
|
|
@ -137,7 +137,7 @@ public class ThreadFragment extends StatusListFragment{
|
|||
}
|
||||
|
||||
private List<Status> filterStatuses(List<Status> statuses){
|
||||
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD);
|
||||
StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext());
|
||||
return statuses.stream()
|
||||
.filter(statusFilterPredicate)
|
||||
.collect(Collectors.toList());
|
||||
|
|
|
@ -26,7 +26,7 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
|
|||
@Override
|
||||
public void onSuccess(List<Status> result){
|
||||
if (getActivity() == null) return;
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
}).exec(accountID);
|
||||
|
@ -42,4 +42,10 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop
|
|||
public boolean isOnTop() {
|
||||
return isRecyclerViewOnTop(list);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public class FederatedTimelineFragment extends StatusListFragment {
|
|||
if(!result.isEmpty())
|
||||
maxID=result.get(result.size()-1).id;
|
||||
if (getActivity() == null) return;
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
|
@ -47,4 +47,9 @@ public class FederatedTimelineFragment extends StatusListFragment {
|
|||
super.onViewCreated(view, savedInstanceState);
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public class LocalTimelineFragment extends StatusListFragment {
|
|||
if(!result.isEmpty())
|
||||
maxID=result.get(result.size()-1).id;
|
||||
if (getActivity() == null) return;
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, Filter.FilterContext.PUBLIC)).collect(Collectors.toList());
|
||||
result=result.stream().filter(new StatusFilterPredicate(accountID, getFilterContext())).collect(Collectors.toList());
|
||||
onDataLoaded(result, !result.isEmpty());
|
||||
}
|
||||
})
|
||||
|
@ -46,4 +46,9 @@ public class LocalTimelineFragment extends StatusListFragment {
|
|||
super.onViewCreated(view, savedInstanceState);
|
||||
bannerHelper.maybeAddBanner(contentWrap);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return Filter.FilterContext.PUBLIC;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.joinmastodon.android.fragments.IsOnTop;
|
|||
import org.joinmastodon.android.fragments.ProfileFragment;
|
||||
import org.joinmastodon.android.fragments.ThreadFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Hashtag;
|
||||
import org.joinmastodon.android.model.SearchResult;
|
||||
import org.joinmastodon.android.model.SearchResults;
|
||||
|
@ -80,7 +81,7 @@ public class SearchFragment extends BaseStatusListFragment<SearchResult> impleme
|
|||
return switch(s.type){
|
||||
case ACCOUNT -> Collections.singletonList(new AccountStatusDisplayItem(s.id, this, s.account));
|
||||
case HASHTAG -> Collections.singletonList(new HashtagStatusDisplayItem(s.id, this, s.hashtag));
|
||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null);
|
||||
case STATUS -> StatusDisplayItem.buildItems(this, s.status, accountID, s, knownAccounts, false, true, null, Filter.FilterContext.PUBLIC);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses;
|
|||
import org.joinmastodon.android.events.FinishReportFragmentsEvent;
|
||||
import org.joinmastodon.android.fragments.StatusListFragment;
|
||||
import org.joinmastodon.android.model.Account;
|
||||
import org.joinmastodon.android.model.Filter;
|
||||
import org.joinmastodon.android.model.Status;
|
||||
import org.joinmastodon.android.ui.displayitems.AudioStatusDisplayItem;
|
||||
import org.joinmastodon.android.ui.displayitems.HeaderStatusDisplayItem;
|
||||
|
@ -261,4 +262,9 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{
|
|||
protected boolean wantsOverlaySystemNavigation(){
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterContext getFilterContext() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,16 +2,18 @@ package org.joinmastodon.android.model;
|
|||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import org.joinmastodon.android.api.ObjectValidationException;
|
||||
import org.joinmastodon.android.api.RequiredField;
|
||||
import org.parceler.Parcel;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Parcel
|
||||
public class Filter extends BaseModel{
|
||||
|
|
|
@ -81,18 +81,10 @@ public abstract class StatusDisplayItem{
|
|||
};
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification){
|
||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, Filter.FilterContext.HOME);
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, Filter.FilterContext filterContext){
|
||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, filterContext);
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate){
|
||||
return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, disableTranslate, Filter.FilterContext.HOME);
|
||||
}
|
||||
|
||||
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment<?> fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){
|
||||
String parentID=parentObject.getID();
|
||||
ArrayList<StatusDisplayItem> items=new ArrayList<>();
|
||||
|
@ -102,12 +94,8 @@ public abstract class StatusDisplayItem{
|
|||
args.putString("account", accountID);
|
||||
ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null;
|
||||
|
||||
List<Filter> filters = AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream()
|
||||
.filter(f -> f.context.contains(filterContext)).collect(Collectors.toList());
|
||||
StatusFilterPredicate filterPredicate = new StatusFilterPredicate(filters);
|
||||
|
||||
if(!statusForContent.filterRevealed){
|
||||
statusForContent.filterRevealed = !filterPredicate.testHasStatusWarning(status, filterContext);
|
||||
if (!statusForContent.filterRevealed) {
|
||||
statusForContent.filterRevealed = new StatusFilterPredicate(accountID, filterContext, Filter.FilterAction.WARN).test(status);
|
||||
}
|
||||
|
||||
ReblogOrReplyLineStatusDisplayItem replyLine = null;
|
||||
|
|
|
@ -8,51 +8,53 @@ import java.time.Instant;
|
|||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class StatusFilterPredicate implements Predicate<Status>{
|
||||
private final List<Filter> filters;
|
||||
private final Filter.FilterContext context;
|
||||
private final Filter.FilterAction action;
|
||||
|
||||
public StatusFilterPredicate(List<Filter> filters){
|
||||
public StatusFilterPredicate(List<Filter> filters, Filter.FilterContext context){
|
||||
this.filters=filters;
|
||||
this.context = context;
|
||||
this.action = Filter.FilterAction.HIDE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context null makes the predicate pass automatically
|
||||
*/
|
||||
public StatusFilterPredicate(String accountID, Filter.FilterContext context){
|
||||
filters=AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream().filter(f->f.context.contains(context)).collect(Collectors.toList());
|
||||
this(accountID, context, Filter.FilterAction.HIDE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context null makes the predicate pass automatically
|
||||
* @param action defines what the predicate should check:
|
||||
* should not be hidden or should not display with warning
|
||||
*/
|
||||
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());
|
||||
this.context = context;
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
// TODO: rewrite (see testHasStatusWarning) and generalize
|
||||
@Override
|
||||
public boolean test(Status status){
|
||||
if(status.filtered!=null){
|
||||
if (status.filtered.isEmpty()){
|
||||
return true;
|
||||
}
|
||||
boolean matches=status.filtered.stream()
|
||||
.map(filterResult->filterResult.filter)
|
||||
.filter(filter->filter.expiresAt==null||filter.expiresAt.isAfter(Instant.now()))
|
||||
.anyMatch(filter->filter.filterAction==Filter.FilterAction.HIDE);
|
||||
return !matches;
|
||||
}
|
||||
for(Filter filter:filters){
|
||||
if(filter.matches(status))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (context == null) return true;
|
||||
|
||||
// TODO: move this method elsewhere; it's not part of the actual StatusFilterPredicate
|
||||
public boolean testHasStatusWarning(Status status, Filter.FilterContext context) {
|
||||
if (status.filtered != null) {
|
||||
// use server-provided info on whether this status was filtered
|
||||
if (status.filtered.isEmpty()) return false;
|
||||
return status.filtered.stream()
|
||||
.map(filterResult -> filterResult.filter)
|
||||
.filter(filter -> filter.expiresAt == null || filter.expiresAt.isAfter(Instant.now()))
|
||||
.filter(filter -> filter.context.contains(context))
|
||||
.anyMatch(filter -> filter.filterAction == Filter.FilterAction.WARN);
|
||||
} else {
|
||||
// look through local filters instead
|
||||
return filters.stream().anyMatch(filter -> filter.matches(status));
|
||||
}
|
||||
Stream<Filter> stream = status.filtered != null
|
||||
// use server-provided per-status info (status.filtered) if available
|
||||
? status.filtered.stream().map(f -> f.filter)
|
||||
// or fall back to cached filters
|
||||
: filters.stream().filter(filter -> filter.matches(status));
|
||||
|
||||
return stream
|
||||
// discard expired filters
|
||||
.filter(filter -> filter.expiresAt == null || filter.expiresAt.isAfter(Instant.now()))
|
||||
// only apply filters for given context
|
||||
.filter(filter -> filter.context.contains(context))
|
||||
// treating filterAction = null (from filters list) as FilterAction.HIDE
|
||||
.noneMatch(filter -> filter.filterAction == null ? action == Filter.FilterAction.HIDE : filter.filterAction == action);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue