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 00e577c4c..acc3b5da0 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -40,6 +40,7 @@ import org.joinmastodon.android.ui.displayitems.PollFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.PollOptionStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.StatusDisplayItem; import org.joinmastodon.android.ui.displayitems.TextStatusDisplayItem; +import org.joinmastodon.android.ui.displayitems.WarningFilteredStatusDisplayItem; import org.joinmastodon.android.ui.photoviewer.PhotoViewer; import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; import org.joinmastodon.android.ui.utils.UiUtils; @@ -504,6 +505,14 @@ public abstract class BaseStatusListFragment exten public void onGapClick(GapStatusDisplayItem.Holder item){} + public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warning){ + int startPos = warning.getAbsoluteAdapterPosition(); + displayItems.remove(startPos); + displayItems.addAll(startPos, warning.filteredItems); + adapter.notifyItemRangeInserted(startPos, warning.filteredItems.size() - 1); + if (startPos == 0) scrollToTop(); + } + public String getAccountID(){ return accountID; } diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java index a7a47cb4e..a4b71f61c 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/NotificationsListFragment.java @@ -14,6 +14,7 @@ import org.joinmastodon.android.api.session.AccountSessionManager; import org.joinmastodon.android.events.PollUpdatedEvent; import org.joinmastodon.android.events.RemoveAccountPostsEvent; import org.joinmastodon.android.model.Account; +import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.PaginatedResponse; import org.joinmastodon.android.model.Status; @@ -91,7 +92,7 @@ public class NotificationsListFragment extends BaseStatusListFragment items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n); + ArrayList items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts, titleItem!=null, titleItem==null, n, false, Filter.FilterContext.NOTIFICATIONS); if(titleItem!=null){ for(StatusDisplayItem item:items){ if(item instanceof ImageStatusDisplayItem imgItem){ 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 a9ce3120e..b8e68b272 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/StatusListFragment.java @@ -12,6 +12,7 @@ import org.joinmastodon.android.events.StatusCountersUpdatedEvent; import org.joinmastodon.android.events.StatusCreatedEvent; import org.joinmastodon.android.events.StatusDeletedEvent; import org.joinmastodon.android.events.StatusUpdatedEvent; +import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.displayitems.ExtendedFooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; @@ -30,7 +31,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment{ protected EventListener eventListener=new EventListener(); protected List buildDisplayItems(Status s){ - return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true, null); + return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts, false, true, null, Filter.FilterContext.HOME); } @Override @@ -56,6 +57,7 @@ public abstract class StatusListFragment extends BaseStatusListFragment{ Status status=getContentStatusByID(id); if(status==null) return; + status.filterRevealed = true; Bundle args=new Bundle(); args.putString("account", accountID); args.putParcelable("status", Parcels.wrap(status)); 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 f7b394765..766abc111 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Filter.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Filter.java @@ -19,6 +19,7 @@ public class Filter extends BaseModel{ public String id; @RequiredField public String phrase; + public String title; public transient EnumSet context=EnumSet.noneOf(FilterContext.class); public Instant expiresAt; public boolean irreversible; @@ -50,6 +51,7 @@ public class Filter extends BaseModel{ else pattern=Pattern.compile(Pattern.quote(phrase), Pattern.CASE_INSENSITIVE); } + if (title == null) title = phrase; return pattern.matcher(text).find(); } @@ -61,6 +63,7 @@ public class Filter extends BaseModel{ public String toString(){ return "Filter{"+ "id='"+id+'\''+ + ", title='"+title+'\''+ ", phrase='"+phrase+'\''+ ", context="+context+ ", expiresAt="+expiresAt+ 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 371a9fafd..139d1857d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Status.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Status.java @@ -58,6 +58,7 @@ public class Status extends BaseModel implements DisplayItemsParent{ public boolean bookmarked; public boolean pinned; + public transient boolean filterRevealed; public transient boolean spoilerRevealed; public transient boolean hasGapAfter; private transient String strippedText; 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 55217f6a1..012f06596 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 @@ -19,6 +19,7 @@ import org.joinmastodon.android.fragments.ThreadFragment; import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.DisplayItemsParent; +import org.joinmastodon.android.model.Filter; import org.joinmastodon.android.model.Hashtag; import org.joinmastodon.android.model.Notification; import org.joinmastodon.android.model.Poll; @@ -26,6 +27,7 @@ import org.joinmastodon.android.model.ScheduledStatus; import org.joinmastodon.android.model.Status; import org.joinmastodon.android.ui.PhotoLayoutHelper; import org.joinmastodon.android.ui.text.HtmlParser; +import org.joinmastodon.android.utils.StatusFilterPredicate; import org.parceler.Parcels; import java.util.ArrayList; @@ -79,21 +81,39 @@ public abstract class StatusDisplayItem{ case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent); case GAP -> new GapStatusDisplayItem.Holder(activity, parent); case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent); + case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent); }; } - 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); + 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, boolean disableTranslate){ + 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<>(); + Status statusForContent=status.getContentStatus(); Bundle args=new Bundle(); 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.filterRevealed){ + statusForContent.filterRevealed = filterPredicate.testWithWarning(status); + } + if(status.reblog!=null){ boolean isOwnPost = AccountSessionManager.getInstance().isSelf(fragment.getAccountID(), status.account); items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName), status.account.emojis, R.drawable.ic_fluent_arrow_repeat_all_20_filled, isOwnPost ? status.visibility : null, i->{ @@ -170,6 +190,13 @@ public abstract class StatusDisplayItem{ item.inset=inset; item.index=i++; } + + if (!statusForContent.filterRevealed) { + return new ArrayList<>(List.of( + new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items) + )); + } + return items; } @@ -196,6 +223,7 @@ public abstract class StatusDisplayItem{ ACCOUNT, HASHTAG, GAP, + WARNING, EXTENDED_FOOTER } @@ -205,7 +233,7 @@ public abstract class StatusDisplayItem{ } public Holder(Context context, int layout, ViewGroup parent){ - super(context, layout, parent); + super(context, layout, parent); } public String getItemID(){ 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 new file mode 100644 index 000000000..fd1e91259 --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java @@ -0,0 +1,52 @@ +package org.joinmastodon.android.ui.displayitems; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.fragments.BaseStatusListFragment; +import org.joinmastodon.android.model.Status; + +import java.util.List; + +public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{ + public boolean loading; + public final Status status; + public List filteredItems; + + public WarningFilteredStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, List filteredItems){ + super(parentID, parentFragment); + this.status=status; + this.filteredItems = filteredItems; + } + + @Override + public Type getType(){ + return Type.WARNING; + } + + public static class Holder extends StatusDisplayItem.Holder{ + public final View warningWrap; + public final TextView text; + public List filteredItems; + + public Holder(Context context, ViewGroup parent) { + super(context, R.layout.display_item_filter_warning, parent); + warningWrap=findViewById(R.id.warning_wrap); + text=findViewById(R.id.text); + } + + @Override + public void onBind(WarningFilteredStatusDisplayItem item) { + filteredItems = item.filteredItems; + text.setText(item.parentFragment.getString(R.string.sk_filtered, item.status.filtered.get(item.status.filtered.size() -1).filter.title)); + } + + @Override + public void onClick() { + item.parentFragment.onWarningClick(this); + } + } +} 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 4555cdfdb..9e4b91413 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/utils/StatusFilterPredicate.java +++ b/mastodon/src/main/java/org/joinmastodon/android/utils/StatusFilterPredicate.java @@ -38,4 +38,22 @@ public class StatusFilterPredicate implements Predicate{ } return true; } + + 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; + } } diff --git a/mastodon/src/main/res/layout/display_item_filter_warning.xml b/mastodon/src/main/res/layout/display_item_filter_warning.xml new file mode 100644 index 000000000..a2ac8bf86 --- /dev/null +++ b/mastodon/src/main/res/layout/display_item_filter_warning.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/mastodon/src/main/res/values/strings_sk.xml b/mastodon/src/main/res/values/strings_sk.xml index ba36d0aa8..aaf19c3f9 100644 --- a/mastodon/src/main/res/values/strings_sk.xml +++ b/mastodon/src/main/res/values/strings_sk.xml @@ -252,4 +252,5 @@ Server version: %s Poll results Prefix reply CW with “re:” + Filtered: %s \ No newline at end of file