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 b28bfe6d3..94a8e6be4 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/BaseStatusListFragment.java @@ -42,6 +42,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; @@ -513,6 +514,26 @@ public abstract class BaseStatusListFragment exten updateImagesSpoilerState(status, itemID); } +// public void onRevealFilteredClick(TextStatusDisplayItem.Holder holder){ +// Status status=holder.getItem().status; +// revealFiltered(status, holder.getItemID()); +// } + + public void onRevealFilteredClick(WarningFilteredStatusDisplayItem.Holder holder){ + Status status=holder.getItem().status; +// revealFiltered(status, holder.getItemID()); + } + + protected void revealFiltered(Status status, ArrayList showedItems){ + status.filterRevealed=true; + + } + +// public void notifyItemsChanged(int adapterPosition){ +// adapter.notifyItemChanged(adapterPosition); +// } + + public void onVisibilityIconClick(HeaderStatusDisplayItem.Holder holder){ Status status=holder.getItem().status; status.spoilerRevealed=!status.spoilerRevealed; @@ -543,6 +564,16 @@ public abstract class BaseStatusListFragment exten public void onGapClick(GapStatusDisplayItem.Holder item){} + public void onWarningClick(WarningFilteredStatusDisplayItem.Holder warningItem){ + int i = warningItem.getAbsoluteAdapterPosition(); + displayItems.remove(warningItem.getAbsoluteAdapterPosition()); + for(StatusDisplayItem item:warningItem.filteredItems){ + displayItems.add(i, item); + i++; + } + adapter.notifyItemChanged(warningItem.getAbsoluteAdapterPosition()); + } + public String getAccountID(){ return accountID; } 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..ea34b8402 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/Filter.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/Filter.java @@ -18,6 +18,8 @@ public class Filter extends BaseModel{ @RequiredField public String id; @RequiredField + public String title; + @RequiredField public String phrase; public transient EnumSet context=EnumSet.noneOf(FilterContext.class); public Instant expiresAt; @@ -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 99ec54ac3..da9f3393e 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,8 @@ public class Status extends BaseModel implements DisplayItemsParent{ public boolean bookmarked; public boolean pinned; + public 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/AudioStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/AudioStatusDisplayItem.java index 3d3acef9c..5ef26577d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/AudioStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/AudioStatusDisplayItem.java @@ -105,6 +105,7 @@ public class AudioStatusDisplayItem extends StatusDisplayItem{ }else{ seekBar.setEnabled(false); } + } private void onPlayPauseClick(View v){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java index f38f632c7..6a694b7f7 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/FooterStatusDisplayItem.java @@ -153,6 +153,7 @@ public class FooterStatusDisplayItem extends StatusDisplayItem{ bookmark.setSelected(item.status.bookmarked); boost.setEnabled(item.status.visibility==StatusPrivacy.PUBLIC || item.status.visibility==StatusPrivacy.UNLISTED || item.status.visibility==StatusPrivacy.LOCAL || (item.status.visibility==StatusPrivacy.PRIVATE && item.status.account.id.equals(AccountSessionManager.getInstance().getAccount(item.accountID).self.id))); + } private void bindButton(TextView btn, long count){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java index f959e7ba4..239641419 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/HeaderStatusDisplayItem.java @@ -177,7 +177,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ })); optionsMenu=new PopupMenu(activity, more); + optionsMenu.inflate(R.menu.post); + optionsMenu.setOnMenuItemClickListener(menuItem->{ Account account=item.user; int id=menuItem.getItemId(); @@ -275,11 +277,29 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ args.putString("profileDisplayUsername", account.getDisplayUsername()); Nav.go(item.parentFragment.getActivity(), ListTimelinesFragment.class, args); } + + if(!item.status.filterRevealed){ + this.itemView.setVisibility(View.GONE); + ViewGroup.LayoutParams params = this.itemView.getLayoutParams(); + params.height = 0; + params.width = 0; + this.itemView.setLayoutParams(params); +// item.parentFragment.notifyItemsChanged(this.getAbsoluteAdapterPosition()); + } return true; }); UiUtils.enablePopupMenuIcons(activity, optionsMenu); + } +// public void setFilteredShown(){ +// this.itemView.setVisibility(View.VISIBLE); +// params = this.itemView.getLayoutParams(); +// params.height = 0; +// params.width = 0; +// this.itemView.setLayoutParams(params); +// } + private void populateAccountsMenu(Menu menu) { List sessions=AccountSessionManager.getInstance().getLoggedInAccounts(); sessions.stream().filter(s -> !s.getID().equals(item.accountID)).forEach(s -> { @@ -299,20 +319,6 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ botIcon.setColorFilter(username.getCurrentTextColor()); separator.setVisibility(View.VISIBLE); -// if(item.user.bot){ -// SpannableStringBuilder ssb = new SpannableStringBuilder(); -// ssb.append('@'+item.user.acct); -// ssb.append(" "); -// Drawable botIcon=username.getResources().getDrawable(R.drawable.ic_bot, itemView.getContext().getTheme()).mutate(); -// botIcon.setBounds(0, 0, botIcon.getIntrinsicWidth(), botIcon.getIntrinsicHeight()); -// botIcon.setTint(username.getCurrentTextColor()); -// ssb.append(itemView.getContext().getString(R.string.manually_approves_followers), new ImageSpan(botIcon, ImageSpan.ALIGN_BASELINE), 0); -// username.setPaddingRelative(0,0,16,0); -// username.setText(ssb); -// } - -// username.setCompoundDrawablesWithIntrinsicBounds(item.user.bot ? R.drawable.ic_fluent_bot_24_filled : 0, 0, 0, 0); - if (item.scheduledStatus!=null) if (item.scheduledStatus.scheduledAt.isAfter(CreateStatus.DRAFTS_AFTER_INSTANT)) { timestamp.setText(R.string.sk_draft); @@ -393,6 +399,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ more.setContentDescription(desc); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) more.setTooltipText(desc); + } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java index 782f94e17..fa1061b2d 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ImageStatusDisplayItem.java @@ -73,6 +73,7 @@ public abstract class ImageStatusDisplayItem extends StatusDisplayItem{ photo.setImageDrawable(crossfadeDrawable); photo.setContentDescription(TextUtils.isEmpty(item.attachment.description) ? item.parentFragment.getString(R.string.media_no_description) : item.attachment.description); didClear=false; + } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/LinkCardStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/LinkCardStatusDisplayItem.java index 42ac3b429..eb47c2790 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/LinkCardStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/LinkCardStatusDisplayItem.java @@ -84,6 +84,7 @@ public class LinkCardStatusDisplayItem extends StatusDisplayItem{ photo.setImageDrawable(crossfadeDrawable); didClear=false; } + } @Override diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java index bbfc7a082..cc5882dc2 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/PhotoStatusDisplayItem.java @@ -106,6 +106,15 @@ public class PhotoStatusDisplayItem extends ImageStatusDisplayItem{ altTextWrapper.setVisibility(View.GONE); } } + + if(!item.status.filterRevealed){ + this.itemView.setVisibility(View.GONE); + ViewGroup.LayoutParams params = this.itemView.getLayoutParams(); + params.height = 0; + params.width = 0; + this.itemView.setLayoutParams(params); +// item.parentFragment.notifyItemsChanged(this.getAbsoluteAdapterPosition()); + } } private void onShowHideClick(View v){ diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java index 366d5ba9f..df198f823 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/ReblogOrReplyLineStatusDisplayItem.java @@ -100,6 +100,7 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ if (visibilityText != 0) text.setContentDescription(item.text + " (" + ctx.getString(visibilityText) + ")"); if(Build.VERSION.SDK_INT new AccountStatusDisplayItem.Holder(activity, parent); case HASHTAG -> new HashtagStatusDisplayItem.Holder(activity, parent); case GAP -> new GapStatusDisplayItem.Holder(activity, parent); + case WARNING -> new WarningFilteredStatusDisplayItem.Holder(activity, parent); case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent); }; } @@ -89,11 +93,19 @@ public abstract class StatusDisplayItem{ public static ArrayList buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map knownAccounts, boolean inset, boolean addFooter, Notification notification, boolean disableTranslate){ String parentID=parentObject.getID(); ArrayList items=new ArrayList<>(); + + ArrayList filtered=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(Filter.FilterContext.HOME)).collect(Collectors.toList()); + StatusFilterPredicate filterPredicate = new StatusFilterPredicate(filters); + + 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->{ @@ -126,11 +138,12 @@ public abstract class StatusDisplayItem{ } ))); } + HeaderStatusDisplayItem header; items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null, notification, scheduledStatus)); - if(!TextUtils.isEmpty(statusForContent.content)) + if(!TextUtils.isEmpty(statusForContent.content)){ items.add(new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent, disableTranslate)); - else + } else header.needBottomPadding=true; List imageAttachments=statusForContent.mediaAttachments.stream().filter(att->att.type.isImage()).collect(Collectors.toList()); if(!imageAttachments.isEmpty()){ @@ -162,14 +175,22 @@ public abstract class StatusDisplayItem{ } if(addFooter){ items.add(new FooterStatusDisplayItem(parentID, fragment, statusForContent, accountID)); - if(status.hasGapAfter && !(fragment instanceof ThreadFragment)) + if(status.hasGapAfter && !(fragment instanceof ThreadFragment)){ items.add(new GapStatusDisplayItem(parentID, fragment)); + } } + int i=1; for(StatusDisplayItem item:items){ item.inset=inset; item.index=i++; } + + if(!statusForContent.filterRevealed){ + filtered.add(new WarningFilteredStatusDisplayItem(parentID, fragment, statusForContent, items)); + return filtered; + } + return items; } @@ -196,6 +217,7 @@ public abstract class StatusDisplayItem{ ACCOUNT, HASHTAG, GAP, + WARNING, EXTENDED_FOOTER } diff --git a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java index 1da7d1cd6..256d2b6f6 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/TextStatusDisplayItem.java @@ -156,6 +156,9 @@ public class TextStatusDisplayItem extends StatusDisplayItem{ ? View.VISIBLE : View.GONE); translateButton.setText(item.translated ? R.string.sk_translate_show_original : R.string.sk_translate_post); translateInfo.setText(item.translated ? itemView.getResources().getString(R.string.sk_translated_using, item.translation.provider) : ""); + + + translateButton.setOnClickListener(v->{ if (item.translation == null) { translateProgress.setVisibility(View.VISIBLE); @@ -186,6 +189,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{ rebind(); } }); + } @Override 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..89ad2f56b --- /dev/null +++ b/mastodon/src/main/java/org/joinmastodon/android/ui/displayitems/WarningFilteredStatusDisplayItem.java @@ -0,0 +1,62 @@ +package org.joinmastodon.android.ui.displayitems; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import org.joinmastodon.android.R; +import org.joinmastodon.android.fragments.BaseStatusListFragment; +import org.joinmastodon.android.model.Status; +import org.joinmastodon.android.ui.drawables.SawtoothTearDrawable; + +import java.util.ArrayList; + +// Mind the gap! +public class WarningFilteredStatusDisplayItem extends StatusDisplayItem{ + public boolean loading; + public final Status status; + public ArrayList filteredItems; + + public WarningFilteredStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status, ArrayList items){ + super(parentID, parentFragment); + this.status=status; + this.filteredItems = items; + } + + @Override + public Type getType(){ + return Type.WARNING; + } + + public static class Holder extends StatusDisplayItem.Holder{ + public final View warningWrap; + public final Button showBtn; + public final TextView text; + public ArrayList filteredItems; + + public Holder(Context context, ViewGroup parent){ + super(context, R.layout.display_item_warning, parent); + warningWrap=findViewById(R.id.warning_wrap); + showBtn=findViewById(R.id.reveal_btn); + showBtn.setOnClickListener(i -> item.parentFragment.onWarningClick(this)); + text=findViewById(R.id.text); +// 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 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 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_warning.xml b/mastodon/src/main/res/layout/display_item_warning.xml new file mode 100644 index 000000000..d618c2bd3 --- /dev/null +++ b/mastodon/src/main/res/layout/display_item_warning.xml @@ -0,0 +1,31 @@ + + + + + +