Filtered posts in timelines (AND-8)

This commit is contained in:
Grishka
2023-06-07 04:47:54 +03:00
parent a24b4363d7
commit 17957b69d1
22 changed files with 253 additions and 148 deletions

View File

@@ -3,6 +3,7 @@ package org.joinmastodon.android.ui.displayitems;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -12,6 +13,7 @@ import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.OutlineProviders;
import org.joinmastodon.android.ui.drawables.SpoilerStripesDrawable;
import org.joinmastodon.android.ui.drawables.TiledDrawable;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.CustomEmojiHelper;
@@ -25,18 +27,25 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
public final ArrayList<StatusDisplayItem> contentItems=new ArrayList<>();
private final CharSequence parsedTitle;
private final CustomEmojiHelper emojiHelper;
private final Type type;
public SpoilerStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, Status status){
public SpoilerStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, String title, Status status, Type type){
super(parentID, parentFragment);
this.status=status;
parsedTitle=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
emojiHelper=new CustomEmojiHelper();
emojiHelper.setText(parsedTitle);
this.type=type;
if(TextUtils.isEmpty(title)){
parsedTitle=HtmlParser.parseCustomEmoji(status.spoilerText, status.emojis);
emojiHelper=new CustomEmojiHelper();
emojiHelper.setText(parsedTitle);
}else{
parsedTitle=title;
emojiHelper=null;
}
}
@Override
public int getImageCount(){
return emojiHelper.getImageCount();
return emojiHelper==null ? 0 : emojiHelper.getImageCount();
}
@Override
@@ -46,14 +55,14 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
@Override
public Type getType(){
return Type.SPOILER;
return type;
}
public static class Holder extends StatusDisplayItem.Holder<SpoilerStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView title, action;
private final View button;
public Holder(Context context, ViewGroup parent){
public Holder(Context context, ViewGroup parent, Type type){
super(context, R.layout.display_item_spoiler, parent);
title=findViewById(R.id.spoiler_title);
action=findViewById(R.id.spoiler_action);
@@ -62,8 +71,14 @@ public class SpoilerStatusDisplayItem extends StatusDisplayItem{
button.setOutlineProvider(OutlineProviders.roundedRect(8));
button.setClipToOutline(true);
LayerDrawable spoilerBg=(LayerDrawable) button.getBackground().mutate();
spoilerBg.setDrawableByLayerId(R.id.left_drawable, new SpoilerStripesDrawable(true));
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable(false));
if(type==Type.SPOILER){
spoilerBg.setDrawableByLayerId(R.id.left_drawable, new SpoilerStripesDrawable(true));
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new SpoilerStripesDrawable(false));
}else if(type==Type.FILTER_SPOILER){
Drawable texture=context.getDrawable(R.drawable.filter_banner_stripe_texture);
spoilerBg.setDrawableByLayerId(R.id.left_drawable, new TiledDrawable(texture));
spoilerBg.setDrawableByLayerId(R.id.right_drawable, new TiledDrawable(texture));
}
button.setBackground(spoilerBg);
button.setOnClickListener(v->item.parentFragment.onRevealSpoilerClick(this));
}

View File

@@ -2,11 +2,11 @@ package org.joinmastodon.android.ui.displayitems;
import android.app.Activity;
import android.content.Context;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.view.View;
import android.view.ViewGroup;
import org.joinmastodon.android.GlobalUserPreferences;
import org.joinmastodon.android.R;
import org.joinmastodon.android.api.session.AccountSessionManager;
import org.joinmastodon.android.fragments.BaseStatusListFragment;
@@ -14,6 +14,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.FilterResult;
import org.joinmastodon.android.model.Poll;
import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.PhotoLayoutHelper;
@@ -72,7 +73,7 @@ public abstract class StatusDisplayItem{
case GAP -> new GapStatusDisplayItem.Holder(activity, parent);
case EXTENDED_FOOTER -> new ExtendedFooterStatusDisplayItem.Holder(activity, parent);
case MEDIA_GRID -> new MediaGridStatusDisplayItem.Holder(activity, parent);
case SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent);
case SPOILER, FILTER_SPOILER -> new SpoilerStatusDisplayItem.Holder(activity, parent, type);
case SECTION_HEADER -> new SectionHeaderStatusDisplayItem.Holder(activity, parent);
case NOTIFICATION_HEADER -> new NotificationHeaderStatusDisplayItem.Holder(activity, parent);
};
@@ -106,9 +107,24 @@ public abstract class StatusDisplayItem{
items.add(header=new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID, statusForContent, null));
}
boolean filtered=false;
if(status.filtered!=null){
for(FilterResult filter:status.filtered){
if(filter.filter.isActive()){
filtered=true;
break;
}
}
}
ArrayList<StatusDisplayItem> contentItems;
if(!TextUtils.isEmpty(statusForContent.spoilerText) && AccountSessionManager.get(accountID).getLocalPreferences().showCWs){
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, statusForContent);
if(filtered){
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, fragment.getString(R.string.post_matches_filter_x, status.filtered.get(0).filter.title), statusForContent, Type.FILTER_SPOILER);
items.add(spoilerItem);
contentItems=spoilerItem.contentItems;
statusForContent.spoilerRevealed=false;
}else if(!TextUtils.isEmpty(statusForContent.spoilerText) && AccountSessionManager.get(accountID).getLocalPreferences().showCWs){
SpoilerStatusDisplayItem spoilerItem=new SpoilerStatusDisplayItem(parentID, fragment, null, statusForContent, Type.SPOILER);
items.add(spoilerItem);
contentItems=spoilerItem.contentItems;
}else{
@@ -116,7 +132,11 @@ public abstract class StatusDisplayItem{
}
if(!TextUtils.isEmpty(statusForContent.content)){
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID), fragment, statusForContent);
SpannableStringBuilder parsedText=HtmlParser.parse(statusForContent.content, statusForContent.emojis, statusForContent.mentions, statusForContent.tags, accountID);
if(filtered){
HtmlParser.applyFilterHighlights(fragment.getActivity(), parsedText, status.filtered);
}
TextStatusDisplayItem text=new TextStatusDisplayItem(parentID, parsedText, fragment, statusForContent);
text.reduceTopPadding=header==null;
contentItems.add(text);
}else if(header!=null){
@@ -192,7 +212,8 @@ public abstract class StatusDisplayItem{
SPOILER,
SECTION_HEADER,
HEADER_CHECKABLE,
NOTIFICATION_HEADER
NOTIFICATION_HEADER,
FILTER_SPOILER
}
public static abstract class Holder<T extends StatusDisplayItem> extends BindableViewHolder<T> implements UsableRecyclerView.DisableableClickable{

View File

@@ -0,0 +1,48 @@
package org.joinmastodon.android.ui.drawables;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class TiledDrawable extends Drawable{
private final Drawable drawable;
public TiledDrawable(Drawable drawable){
this.drawable=drawable;
}
@Override
public void draw(@NonNull Canvas canvas){
Rect bounds=getBounds();
canvas.save();
canvas.clipRect(bounds);
int w=drawable.getIntrinsicWidth();
int h=drawable.getIntrinsicHeight();
for(int y=bounds.top;y<bounds.bottom;y+=h){
for(int x=bounds.left;x<bounds.right;x+=w){
drawable.setBounds(x, y, x+w, y+h);
drawable.draw(canvas);
}
}
canvas.restore();
}
@Override
public void setAlpha(int alpha){
drawable.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter){
drawable.setColorFilter(colorFilter);
}
@Override
public int getOpacity(){
return drawable.getOpacity();
}
}

View File

@@ -1,13 +1,18 @@
package org.joinmastodon.android.ui.text;
import android.content.Context;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.widget.TextView;
import com.twitter.twittertext.Regex;
import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.model.FilterResult;
import org.joinmastodon.android.model.Hashtag;
import org.joinmastodon.android.model.Mention;
import org.joinmastodon.android.ui.utils.UiUtils;
@@ -212,4 +217,22 @@ public class HtmlParser{
}while(matcher.find()); // Find more URLs
return ssb;
}
public static void applyFilterHighlights(Context context, SpannableStringBuilder text, List<FilterResult> filters){
int fgColor=UiUtils.getThemeColor(context, R.attr.colorM3Error);
int bgColor=UiUtils.getThemeColor(context, R.attr.colorM3ErrorContainer);
for(FilterResult filter:filters){
if(!filter.filter.isActive())
continue;;
for(String word:filter.keywordMatches){
Matcher matcher=Pattern.compile("\\b"+Pattern.quote(word)+"\\b", Pattern.CASE_INSENSITIVE).matcher(text);
while(matcher.find()){
ForegroundColorSpan fg=new ForegroundColorSpan(fgColor);
BackgroundColorSpan bg=new BackgroundColorSpan(bgColor);
text.setSpan(bg, matcher.start(), matcher.end(), 0);
text.setSpan(fg, matcher.start(), matcher.end(), 0);
}
}
}
}
}