diff --git a/mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java b/mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java new file mode 100644 index 000000000..0a00fc665 --- /dev/null +++ b/mastodon/src/androidTest/java/org/joinmastodon/android/utils/StatusFilterPredicateTest.java @@ -0,0 +1,81 @@ +package org.joinmastodon.android.utils; + +import static org.joinmastodon.android.model.Filter.FilterAction.*; +import static org.joinmastodon.android.model.Filter.FilterContext.*; +import static org.junit.Assert.*; + +import org.joinmastodon.android.model.Filter; +import org.joinmastodon.android.model.Status; +import org.junit.Test; + +import java.time.Instant; +import java.util.EnumSet; +import java.util.List; + +public class StatusFilterPredicateTest { + + private static final Filter hideMeFilter = new Filter(), warnMeFilter = new Filter(); + private static final List allFilters = List.of(hideMeFilter, warnMeFilter); + + private static final Status + hideInHomePublic = Status.ofFake(null, "hide me, please", Instant.now()), + warnInHomePublic = Status.ofFake(null, "display me with a warning", Instant.now()); + + static { + hideMeFilter.phrase = "hide me"; + hideMeFilter.filterAction = HIDE; + hideMeFilter.context = EnumSet.of(PUBLIC, HOME); + + warnMeFilter.phrase = "warning"; + warnMeFilter.filterAction = WARN; + warnMeFilter.context = EnumSet.of(PUBLIC, HOME); + } + + @Test + public void testHide() { + assertFalse("should not pass because matching filter applies to given context", + new StatusFilterPredicate(allFilters, HOME).test(hideInHomePublic)); + } + + @Test + public void testHideRegardlessOfContext() { + assertTrue("filters without context should always pass", + new StatusFilterPredicate(allFilters, null).test(hideInHomePublic)); + } + + @Test + public void testHideInDifferentContext() { + assertTrue("should pass because matching filter does not apply to given context", + new StatusFilterPredicate(allFilters, THREAD).test(hideInHomePublic)); + } + + @Test + public void testHideWithWarningText() { + assertTrue("should pass because matching filter is for warnings", + new StatusFilterPredicate(allFilters, HOME).test(warnInHomePublic)); + } + + @Test + public void testWarn() { + assertFalse("should not pass because filter applies to given context", + new StatusFilterPredicate(allFilters, HOME, WARN).test(warnInHomePublic)); + } + + @Test + public void testWarnRegardlessOfContext() { + assertTrue("filters without context should always pass", + new StatusFilterPredicate(allFilters, null, WARN).test(warnInHomePublic)); + } + + @Test + public void testWarnInDifferentContext() { + assertTrue("should pass because filter does not apply to given context", + new StatusFilterPredicate(allFilters, THREAD, WARN).test(warnInHomePublic)); + } + + @Test + public void testWarnWithHideText() { + assertTrue("should pass because matching filter is for hiding", + new StatusFilterPredicate(allFilters, HOME, WARN).test(hideInHomePublic)); + } +} \ No newline at end of file diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java b/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java index a7a0ed78b..e4e54b451 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/CacheController.java @@ -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 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 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)); diff --git a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java index b1e73d44d..6cefc76e6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java +++ b/mastodon/src/main/java/org/joinmastodon/android/api/MastodonAPIController.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -51,7 +52,9 @@ public class MastodonAPIController{ .registerTypeAdapter(Status.class, new Status.StatusDeserializer()) .create(); private static WorkerThread thread=new WorkerThread("MastodonAPIController"); - private static OkHttpClient httpClient=new OkHttpClient.Builder().build(); + private static OkHttpClient httpClient=new OkHttpClient.Builder() + .readTimeout(5, TimeUnit.MINUTES) + .build(); private AccountSession session; private static List badDomains = new ArrayList<>(); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java index 2c3050ee5..424cea88c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/AccountTimelineFragment.java @@ -3,10 +3,6 @@ package org.joinmastodon.android.fragments; import android.app.Activity; import android.os.Bundle; import android.view.View; -import android.view.animation.TranslateAnimation; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.accounts.GetAccountStatuses; @@ -64,7 +60,12 @@ public class AccountTimelineFragment extends StatusListFragment{ @Override public void onSuccess(List 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()); } }) @@ -85,7 +86,8 @@ public class AccountTimelineFragment extends StatusListFragment{ } protected void onStatusCreated(StatusCreatedEvent ev){ - if(!AccountSessionManager.getInstance().isSelf(accountID, ev.status.account)) + AccountSessionManager asm = AccountSessionManager.getInstance(); + if(!asm.isSelf(accountID, ev.status.account) || !asm.isSelf(accountID, user)) return; if(filter==GetAccountStatuses.Filter.PINNED) return; if(filter==GetAccountStatuses.Filter.DEFAULT){ @@ -123,4 +125,10 @@ public class AccountTimelineFragment extends StatusListFragment{ protected void onRemoveAccountPostsEvent(RemoveAccountPostsEvent ev){ // no-op } + + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.ACCOUNT; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java index 9b5820f95..0d25daa52 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -69,7 +69,7 @@ import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.V; import me.grishka.appkit.views.UsableRecyclerView; -public abstract class BaseStatusListFragment extends BaseRecyclerFragment implements PhotoViewerHost, ScrollableToTop, DomainDisplay{ +public abstract class BaseStatusListFragment extends BaseRecyclerFragment implements PhotoViewerHost, ScrollableToTop, HasFab, DomainDisplay{ protected ArrayList displayItems=new ArrayList<>(); protected DisplayItemsAdapter adapter; protected String accountID; @@ -271,34 +271,39 @@ public abstract class BaseStatusListFragment exten }); } + @Override public @Nullable View getFab() { if (getParentFragment() instanceof HasFab l) return l.getFab(); else return fab; } - public void animateFab(boolean show) { + @Override + public void showFab() { View fab = getFab(); - if (fab == null) return; - if (show && fab.getVisibility() != View.VISIBLE) { - fab.setVisibility(View.VISIBLE); - TranslateAnimation animate = new TranslateAnimation( - 0, - 0, - fab.getHeight() * 2, - 0); - animate.setDuration(300); - fab.startAnimation(animate); - } else if (!show && fab.getVisibility() == View.VISIBLE) { - TranslateAnimation animate = new TranslateAnimation( - 0, - 0, - 0, - fab.getHeight() * 2); - animate.setDuration(300); - fab.startAnimation(animate); - fab.setVisibility(View.INVISIBLE); - scrollDiff = 0; - } + if (fab == null || fab.getVisibility() == View.VISIBLE) return; + fab.setVisibility(View.VISIBLE); + TranslateAnimation animate = new TranslateAnimation( + 0, + 0, + fab.getHeight() * 2, + 0); + animate.setDuration(300); + fab.startAnimation(animate); + } + + @Override + public void hideFab() { + View fab = getFab(); + if (fab == null || fab.getVisibility() != View.VISIBLE) return; + TranslateAnimation animate = new TranslateAnimation( + 0, + 0, + 0, + fab.getHeight() * 2); + animate.setDuration(300); + fab.startAnimation(animate); + fab.setVisibility(View.INVISIBLE); + scrollDiff = 0; } @Override @@ -315,10 +320,10 @@ public abstract class BaseStatusListFragment exten View fab = getFab(); if (fab!=null && GlobalUserPreferences.autoHideFab) { if (dy > 0 && fab.getVisibility() == View.VISIBLE) { - animateFab(false); + hideFab(); } else if (dy < 0 && fab.getVisibility() != View.VISIBLE) { - if (list.getChildAt(0).getTop() == 0 || scrollDiff > THRESHOLD) { - animateFab(true); + if (list.getChildAt(0).getTop() == 0 || scrollDiff > 400) { + showFab(); scrollDiff = 0; } else { scrollDiff += Math.abs(dy); diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java index 6fd12d95d..6f863bdd2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BookmarkedStatusListFragment.java @@ -4,6 +4,7 @@ import android.app.Activity; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.statuses.GetBookmarkedStatuses; +import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.Status; @@ -35,4 +36,9 @@ public class BookmarkedStatusListFragment extends StatusListFragment{ }) .exec(accountID); } + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.ACCOUNT; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java index 31fdd8442..41e6e0c7f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/FavoritedStatusListFragment.java @@ -4,6 +4,7 @@ import android.app.Activity; import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.statuses.GetFavoritedStatuses; +import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.HeaderPaginationList; import org.joinmastodon.android.model.Status; @@ -35,4 +36,9 @@ public class FavoritedStatusListFragment extends StatusListFragment{ }) .exec(accountID); } + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.ACCOUNT; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HasFab.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HasFab.java index 9245eafcd..056e80044 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HasFab.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HasFab.java @@ -4,4 +4,6 @@ import android.view.View; public interface HasFab { View getFab(); + void showFab(); + void hideFab(); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java index e2244001a..c7ce498ae 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HashtagTimelineFragment.java @@ -131,7 +131,7 @@ public class HashtagTimelineFragment extends PinnableStatusListFragment { @Override public void onSuccess(List 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()); } }) @@ -162,4 +162,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; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java index 8a2562c49..2424a8abc 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeFragment.java @@ -180,6 +180,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene notificationsFragment=(NotificationsFragment) getChildFragmentManager().getFragment(savedInstanceState, "notificationsFragment"); profileFragment=(ProfileFragment) getChildFragmentManager().getFragment(savedInstanceState, "profileFragment"); currentTab=savedInstanceState.getInt("selectedTab"); + tabBar.selectTab(currentTab); Fragment current=fragmentForTab(currentTab); getChildFragmentManager().beginTransaction() @@ -267,6 +268,7 @@ public class HomeFragment extends AppKitFragment implements OnBackPressedListene getChildFragmentManager().beginTransaction().hide(fragmentForTab(currentTab)).show(newFragment).commit(); maybeTriggerLoading(newFragment); + if (newFragment instanceof HasFab fabulous) fabulous.showFab(); currentTab=tab; ((FragmentStackActivity)getActivity()).invalidateSystemBarColors(this); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java index ed2851dc1..6994b4d84 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTabFragment.java @@ -466,10 +466,20 @@ public class HomeTabFragment extends MastodonToolbarFragment implements Scrollab updateSwitcherIcon(i); } + @Override + public void showFab() { + if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment l) l.showFab(); + } + + @Override + public void hideFab() { + if (fragments[pager.getCurrentItem()] instanceof BaseStatusListFragment l) l.hideFab(); + } + private void updateSwitcherIcon(int i) { timelineIcon.setImageResource(timelines[i].getIcon().iconRes); timelineTitle.setText(timelines[i].getTitle(getContext())); - if (fragments[i] instanceof BaseStatusListFragment l) l.animateFab(true); + showFab(); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java index 6de831e18..5fb3a9cc9 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/HomeTimelineFragment.java @@ -8,8 +8,6 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import org.joinmastodon.android.GlobalUserPreferences; -import org.joinmastodon.android.E; -import org.joinmastodon.android.R; import org.joinmastodon.android.api.requests.markers.SaveMarkers; import org.joinmastodon.android.api.requests.timelines.GetHomeTimeline; import org.joinmastodon.android.api.session.AccountSessionManager; @@ -156,7 +154,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); @@ -235,7 +233,7 @@ public class HomeTimelineFragment extends StatusListFragment { List targetList=displayItems.subList(gapPos, gapPos+1); targetList.clear(); List 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; @@ -288,4 +286,9 @@ public class HomeTimelineFragment extends StatusListFragment { protected boolean shouldRemoveAccountPostsWhenUnfollowing(){ return true; } + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.HOME; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java index fd2155b54..da6322a85 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ListTimelineFragment.java @@ -137,7 +137,7 @@ public class ListTimelineFragment extends PinnableStatusListFragment { @Override public void onSuccess(List 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()); } }) @@ -162,4 +162,10 @@ public class ListTimelineFragment extends PinnableStatusListFragment { protected void onSetFabBottomInset(int inset) { ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin=V.dp(24)+inset; } + + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.HOME; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java index 7b3239e90..2bc81efa1 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -885,6 +885,16 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList return fab; } + @Override + public void showFab() { + if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.showFab(); + } + + @Override + public void hideFab() { + if (getFragmentForPage(pager.getCurrentItem()) instanceof HasFab fabulous) fabulous.hideFab(); + } + private void onScrollChanged(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY){ int topBarsH=getToolbar().getHeight()+statusBarHeight; if(scrollY>avatarBorder.getTop()-topBarsH){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ScheduledStatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ScheduledStatusListFragment.java index 90927aefe..b1676f386 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ScheduledStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ScheduledStatusListFragment.java @@ -80,7 +80,7 @@ public class ScheduledStatusListFragment extends BaseStatusListFragment buildDisplayItems(ScheduledStatus s) { - return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, Filter.FilterContext.HOME); + return StatusDisplayItem.buildItems(this, s.toStatus(), accountID, s, knownAccounts, false, false, null, true, null); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java index a6f1e2122..4cf0e0639 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusEditHistoryFragment.java @@ -56,7 +56,7 @@ public class StatusEditHistoryFragment extends StatusListFragment{ @Override protected List buildDisplayItems(Status s){ - List items=StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, true, false, null, Filter.FilterContext.HOME); + List 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())); @@ -157,4 +157,9 @@ public class StatusEditHistoryFragment extends StatusListFragment{ public boolean isItemEnabled(String id){ return false; } + + @Override + protected Filter.FilterContext getFilterContext() { + return null; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java index 6b911b4a3..90a346024 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java @@ -36,9 +36,11 @@ public abstract class StatusListFragment extends BaseStatusListFragment protected List buildDisplayItems(Status s){ boolean addFooter = !GlobalUserPreferences.spectatorMode || (this instanceof ThreadFragment t && s.id.equals(t.mainStatus.id)); - return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, Filter.FilterContext.HOME); + return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, addFooter, null, getFilterContext()); } + protected abstract Filter.FilterContext getFilterContext(); + @Override protected void addAccountToKnown(Status s){ if(!knownAccounts.containsKey(s.account.id)) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java index 6e55cf69e..504c72e67 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ThreadFragment.java @@ -145,7 +145,7 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{ } private List filterStatuses(List statuses){ - StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,Filter.FilterContext.THREAD); + StatusFilterPredicate statusFilterPredicate=new StatusFilterPredicate(accountID,getFilterContext()); return statuses.stream() .filter(statusFilterPredicate) .collect(Collectors.toList()); @@ -189,4 +189,10 @@ public class ThreadFragment extends StatusListFragment implements DomainDisplay{ public boolean wantsLightNavigationBar(){ return !UiUtils.isDarkTheme(); } + + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.THREAD; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java index dfbf27c8e..1af3735f4 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/DiscoverPostsFragment.java @@ -31,7 +31,7 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop @Override public void onSuccess(List 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); @@ -47,4 +47,10 @@ public class DiscoverPostsFragment extends StatusListFragment implements IsOnTop public boolean isOnTop() { return isRecyclerViewOnTop(list); } + + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.PUBLIC; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java index e939118ed..f5d2bb61c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/FederatedTimelineFragment.java @@ -40,7 +40,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()); } }) @@ -52,4 +52,9 @@ public class FederatedTimelineFragment extends StatusListFragment { super.onViewCreated(view, savedInstanceState); // bannerHelper.maybeAddBanner(contentWrap); } + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.PUBLIC; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java index 24d7d7702..631a8d64f 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/discover/LocalTimelineFragment.java @@ -38,7 +38,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()); } }) @@ -50,4 +50,9 @@ public class LocalTimelineFragment extends StatusListFragment { super.onViewCreated(view, savedInstanceState); bannerHelper.maybeAddBanner(contentWrap); } + + @Override + protected Filter.FilterContext getFilterContext() { + return Filter.FilterContext.PUBLIC; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/CustomWelcomeFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/CustomWelcomeFragment.java index 8a043aa4b..bb1de3548 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/CustomWelcomeFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/onboarding/CustomWelcomeFragment.java @@ -124,6 +124,7 @@ public class CustomWelcomeFragment extends InstanceCatalogFragment { super.onViewCreated(view, savedInstanceState); view.setBackgroundColor(UiUtils.getThemeColor(getActivity(), R.attr.colorWindowBackground)); list.setItemAnimator(new BetterItemAnimator()); + ((UsableRecyclerView) list).setSelector(null); } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java index dff5551a3..a04d0a9a8 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/report/ReportAddPostsChoiceFragment.java @@ -262,4 +262,9 @@ public class ReportAddPostsChoiceFragment extends StatusListFragment{ protected boolean wantsOverlaySystemNavigation(){ return false; } + + @Override + protected Filter.FilterContext getFilterContext() { + return null; + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Filter.java b/mastodon/src/main/java/org/joinmastodon/android/model/Filter.java index 7084ddd7a..019d249a6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Filter.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Filter.java @@ -2,25 +2,24 @@ 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{ - @RequiredField public String id; - @RequiredField - public String title; - @RequiredField public String phrase; + public String title; public transient EnumSet context=EnumSet.noneOf(FilterContext.class); public Instant expiresAt; public boolean irreversible; diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/FilterResult.java b/mastodon/src/main/java/org/joinmastodon/android/model/FilterResult.java index 2b67ef4bf..361a5fac5 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/FilterResult.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/FilterResult.java @@ -1,8 +1,15 @@ package org.joinmastodon.android.model; +import org.joinmastodon.android.api.ObjectValidationException; import org.parceler.Parcel; @Parcel public class FilterResult extends BaseModel { public Filter filter; + + @Override + public void postprocess() throws ObjectValidationException { + super.postprocess(); + if (filter != null) filter.postprocess(); + } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java index 9ce3a174d..f74e45f23 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java @@ -53,6 +53,8 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{ public long favouritesCount; public long repliesCount; public Instant editedAt; + // might not be provided (by older mastodon servers), + // so megalodon will use the locally cached filters if filtered == null public List filtered; public String url; @@ -107,6 +109,9 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{ card.postprocess(); if(reblog!=null) reblog.postprocess(); + if(filtered!=null) + for(FilterResult fr : filtered) + fr.postprocess(); spoilerRevealed=GlobalUserPreferences.alwaysExpandContentWarnings || !sensitive; if (visibility.equals(StatusPrivacy.LOCAL)) localOnly = true; @@ -188,7 +193,6 @@ public class Status extends BaseModel implements DisplayItemsParent, Searchable{ s.mentions = List.of(); s.tags = List.of(); s.emojis = List.of(); - s.filtered = List.of(); return s; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FileStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FileStatusDisplayItem.java index 737d5b082..9fa428f70 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FileStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FileStatusDisplayItem.java @@ -36,7 +36,6 @@ public class FileStatusDisplayItem extends StatusDisplayItem{ public static class Holder extends StatusDisplayItem.Holder{ private final TextView title, domain; - private boolean didClear; public Holder(Context context, ViewGroup parent){ super(context, R.layout.display_item_file, parent); @@ -54,6 +53,21 @@ public class FileStatusDisplayItem extends StatusDisplayItem{ private void onClick(View v){ UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), item.attachment.remoteUrl == null ? item.attachment.url : item.attachment.remoteUrl); + public void onBind(FileStatusDisplayItem item) { + Uri url = Uri.parse(getUrl()); + title.setText(item.attachment.description != null + ? item.attachment.description + : url.getLastPathSegment()); + title.setEllipsize(item.attachment.description != null ? TextUtils.TruncateAt.END : TextUtils.TruncateAt.MIDDLE); + domain.setText(url.getHost()); + } + + private void onClick(View v) { + UiUtils.openURL(itemView.getContext(), item.parentFragment.getAccountID(), getUrl()); + } + + private String getUrl() { + return item.attachment.remoteUrl == null ? item.attachment.url : item.attachment.remoteUrl; } } } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java index 274564a6d..b7e1de674 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/StatusDisplayItem.java @@ -9,7 +9,6 @@ import android.view.ViewGroup; import org.joinmastodon.android.GlobalUserPreferences; import org.joinmastodon.android.R; -import org.joinmastodon.android.api.session.AccountSession; import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.HashtagTimelineFragment; @@ -83,18 +82,10 @@ public abstract class StatusDisplayItem{ }; } - public static ArrayList buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map knownAccounts, boolean inset, boolean addFooter, Notification notification){ - return buildItems(fragment, status, accountID, parentObject, knownAccounts, inset, addFooter, notification, false, Filter.FilterContext.HOME); - } - public static ArrayList buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map 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 buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map 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 buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate, Filter.FilterContext filterContext){ String parentID=parentObject.getID(); ArrayList items=new ArrayList<>(); @@ -104,14 +95,6 @@ public abstract class StatusDisplayItem{ args.putString("account", accountID); ScheduledStatus scheduledStatus = parentObject instanceof ScheduledStatus ? (ScheduledStatus) parentObject : null; - List filters = AccountSessionManager.getInstance().getAccount(accountID).wordFilters.stream() - .filter(f -> f.context.contains(filterContext)).collect(Collectors.toList()); - StatusFilterPredicate filterPredicate = new StatusFilterPredicate(filters); - - if(statusForContent != null && !statusForContent.filterRevealed){ - statusForContent.filterRevealed = filterPredicate.testWithWarning(status); - } - ReblogOrReplyLineStatusDisplayItem replyLine = null; boolean threadReply = statusForContent.inReplyToAccountId != null && statusForContent.inReplyToAccountId.equals(statusForContent.account.id); @@ -186,7 +169,10 @@ public abstract class StatusDisplayItem{ replyLine.needBottomPadding=true; else header.needBottomPadding=true; - List imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage() && !att.type.equals(Attachment.Type.UNKNOWN)).collect(Collectors.toList()); + + List imageAttachments=statusForContent.mediaAttachments.stream() + .filter(att->att.type.isImage() && !att.type.equals(Attachment.Type.UNKNOWN)) + .collect(Collectors.toList()); if(!imageAttachments.isEmpty()){ PhotoLayoutHelper.TiledLayoutResult layout=PhotoLayoutHelper.processThumbs(imageAttachments); items.add(new MediaGridStatusDisplayItem(parentID, fragment, layout, imageAttachments, statusForContent)); @@ -197,22 +183,19 @@ public abstract class StatusDisplayItem{ } } - List fileAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.equals(Attachment.Type.UNKNOWN)).collect(Collectors.toList()); - fileAttachments.forEach(attachment -> { - items.add(new FileStatusDisplayItem(parentID, fragment, attachment, statusForContent)); - }); + statusForContent.mediaAttachments.stream() + .filter(att->att.type.equals(Attachment.Type.UNKNOWN)) + .map(att -> new FileStatusDisplayItem(parentID, fragment, att)) + .forEach(items::add); if(statusForContent.poll!=null){ - buildPollItems(parentID, fragment, statusForContent.poll, items, statusForContent); + buildPollItems(parentID, fragment, statusForContent.poll, items); } if(statusForContent.card!=null && statusForContent.mediaAttachments.isEmpty() && TextUtils.isEmpty(statusForContent.spoilerText)){ items.add(new LinkCardStatusDisplayItem(parentID, fragment, statusForContent)); } if(addFooter){ items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID)); - if(status.hasGapAfter && !(fragment instanceof ThreadFragment)){ - items.add(new GapStatusDisplayItem(parentID, fragment)); - } } int i=1; for(StatusDisplayItem item:items){ @@ -220,20 +203,30 @@ public abstract class StatusDisplayItem{ item.index=i++; } + Filter applyingFilter = null; if (!statusForContent.filterRevealed) { - return new ArrayList<>(List.of( - new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items) - )); + StatusFilterPredicate predicate = new StatusFilterPredicate(accountID, filterContext, Filter.FilterAction.WARN); + statusForContent.filterRevealed = predicate.test(status); + applyingFilter = predicate.getApplyingFilter(); } - return items; + ArrayList result = statusForContent.filterRevealed ? items : + new ArrayList<>(List.of(new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items, applyingFilter))); + + if (addFooter && status.hasGapAfter && !(fragment instanceof ThreadFragment)) { + StatusDisplayItem gap = new GapStatusDisplayItem(parentID, fragment); + gap.index = i++; + result.add(gap); + } + + return result; } - public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List items, Status status){ + public static void buildPollItems(String parentID, BaseStatusListFragment fragment, Poll poll, List items){ for(Poll.Option opt:poll.options){ - items.add(new PollOptionStatusDisplayItem(parentID, poll, opt, fragment, status)); + items.add(new PollOptionStatusDisplayItem(parentID, poll, opt, fragment)); } - items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll, status)); + items.add(new PollFooterStatusDisplayItem(parentID, fragment, poll)); } public enum Type{ @@ -249,9 +242,9 @@ public abstract class StatusDisplayItem{ ACCOUNT, HASHTAG, GAP, - WARNING, EXTENDED_FOOTER, MEDIA_GRID, + WARNING, FILE } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java index 89ad2f56b..9c35fdd87 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.RecyclerView; import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.BaseStatusListFragment; +import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable; @@ -18,20 +19,22 @@ import java.util.ArrayList; // Mind the gap! public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{ - public boolean loading; - public final Status status; - public ArrayList filteredItems; + public boolean loading; + public final Status status; + public List filteredItems; + public Filter applyingFilter; - public WarningFilteredStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, ArrayList items){ - super(parentID, parentFragment); - this.status=status; - this.filteredItems = items; - } + public WarningFilteredStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, List filteredItems, Filter applyingFilter){ + super(parentID, parentFragment); + this.status=status; + this.filteredItems = filteredItems; + this.applyingFilter = applyingFilter; + } - @Override - public Type getType(){ - return Type.WARNING; - } + @Override + public Type getType(){ + return Type.WARNING; + } public static class Holder extends StatusDisplayItem.Holder{ public final View warningWrap; @@ -48,11 +51,11 @@ public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{ // itemView.setOnClickListener(v->item.parentFragment.onRevealFilteredClick(this)); } - @Override - public void onBind(WarningFilteredStatusDisplayItem item){ - filteredItems = item.filteredItems; - text.setText(item.parentFragment.getString(R.string.mo_filtered, item.status.filtered.get(item.status.filtered.size() -1).filter.title)); - } + @Override + public void onBind(WarningFilteredStatusDisplayItem item) { + filteredItems = item.filteredItems; + text.setText(item.parentFragment.getString(R.string.sk_filtered, item.applyingFilter.title)); + } @Override public void onClick(){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/utils/StatusFilterPredicate.java b/mastodon/src/main/java/org/joinmastodon/android/utils/StatusFilterPredicate.java index 9e4b91413..0c96bb9c0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/utils/StatusFilterPredicate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/utils/StatusFilterPredicate.java @@ -6,54 +6,80 @@ import org.joinmastodon.android.model.Status; import java.time.Instant; import java.util.List; +import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; public class StatusFilterPredicate implements Predicate{ private final List filters; + private final Filter.FilterContext context; + private final Filter.FilterAction action; + private Filter applyingFilter; - public StatusFilterPredicate(List filters){ - this.filters=filters; + /** + * @param context null makes the predicate pass automatically + * @param action defines what the predicate should check: + * status should not be hidden or should not display with warning + */ + public StatusFilterPredicate(List filters, Filter.FilterContext context, Filter.FilterAction action){ + this.filters = filters; + this.context = context; + this.action = action; } - public StatusFilterPredicate(String accountID, Filter.FilterContext context){ + public StatusFilterPredicate(List filters, Filter.FilterContext context){ + this(filters, context, Filter.FilterAction.HIDE); + } + + /** + * @param context null makes the predicate pass automatically + * @param action defines what the predicate should check: + * status 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; } + /** + * @param context null makes the predicate pass automatically + */ + public StatusFilterPredicate(String accountID, Filter.FilterContext context){ + this(accountID, context, Filter.FilterAction.HIDE); + } + + /** + * @return whether the status should be displayed without being hidden/warned about. + * will always return true if the context is null. + * true = display this status, + * false = filter this status + */ @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; + + Stream matchingFilters = 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)); + + Optional applyingFilter = matchingFilters + // 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 + .filter(filter -> filter.filterAction == null ? action == Filter.FilterAction.HIDE : filter.filterAction == action) + .findAny(); + + this.applyingFilter = applyingFilter.orElse(null); + return applyingFilter.isEmpty(); } - public boolean testWithWarning(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.WARN); - return !matches; - } - for(Filter filter:filters){ - if(filter.matches(status)) - return false; - } - return true; + public Filter getApplyingFilter() { + return applyingFilter; } } diff --git a/mastodon/src/main/res/drawable/bg_search_button.xml b/mastodon/src/main/res/drawable/bg_search_button.xml new file mode 100644 index 000000000..717bda7ce --- /dev/null +++ b/mastodon/src/main/res/drawable/bg_search_button.xml @@ -0,0 +1,6 @@ + + + + diff --git a/mastodon/src/main/res/layout/display_item_file.xml b/mastodon/src/main/res/layout/display_item_file.xml index 7fc227cdd..9db212e7b 100644 --- a/mastodon/src/main/res/layout/display_item_file.xml +++ b/mastodon/src/main/res/layout/display_item_file.xml @@ -55,5 +55,4 @@ - \ No newline at end of file diff --git a/mastodon/src/main/res/layout/item_instance_custom.xml b/mastodon/src/main/res/layout/item_instance_custom.xml index c8cf010f6..25a2058a3 100644 --- a/mastodon/src/main/res/layout/item_instance_custom.xml +++ b/mastodon/src/main/res/layout/item_instance_custom.xml @@ -8,7 +8,7 @@ + android:background="@drawable/bg_search_button">