Show "in reply to" and add a custom emoji helper

This commit is contained in:
Grishka 2022-02-11 16:30:03 +03:00
parent c9078ca8d7
commit eed64f48fe
8 changed files with 140 additions and 55 deletions

View File

@ -12,6 +12,7 @@ import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem; import org.joinmastodon.android.ui.displayitems.FooterStatusDisplayItem;
@ -22,6 +23,7 @@ import org.joinmastodon.android.ui.photoviewer.PhotoViewer;
import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost; import org.joinmastodon.android.ui.photoviewer.PhotoViewerHost;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -40,6 +42,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected DisplayItemsAdapter adapter; protected DisplayItemsAdapter adapter;
protected String accountID; protected String accountID;
protected PhotoViewer currentPhotoViewer; protected PhotoViewer currentPhotoViewer;
protected HashMap<String, Account> knownAccounts=new HashMap<>();
public BaseStatusListFragment(){ public BaseStatusListFragment(){
super(20); super(20);
@ -59,6 +62,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
@Override @Override
public void onAppendItems(List<T> items){ public void onAppendItems(List<T> items){
super.onAppendItems(items); super.onAppendItems(items);
for(T s:items){
addAccountToKnown(s);
}
for(T s:items){ for(T s:items){
displayItems.addAll(buildDisplayItems(s)); displayItems.addAll(buildDisplayItems(s));
} }
@ -73,6 +79,9 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
protected void prependItems(List<T> items){ protected void prependItems(List<T> items){
data.addAll(0, items); data.addAll(0, items);
int offset=0; int offset=0;
for(T s:items){
addAccountToKnown(s);
}
for(T s:items){ for(T s:items){
List<StatusDisplayItem> toAdd=buildDisplayItems(s); List<StatusDisplayItem> toAdd=buildDisplayItems(s);
displayItems.addAll(offset, toAdd); displayItems.addAll(offset, toAdd);
@ -91,6 +100,7 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
} }
protected abstract List<StatusDisplayItem> buildDisplayItems(T s); protected abstract List<StatusDisplayItem> buildDisplayItems(T s);
protected abstract void addAccountToKnown(T s);
@Override @Override
protected void onHidden(){ protected void onHidden(){
@ -324,5 +334,15 @@ public abstract class BaseStatusListFragment<T extends DisplayItemsParent> exten
public ImageLoaderRequest getImageRequest(int position, int image){ public ImageLoaderRequest getImageRequest(int position, int image){
return displayItems.get(position).getImageRequest(image); return displayItems.get(position).getImageRequest(image);
} }
@Override
public void onViewDetachedFromWindow(@NonNull BindableViewHolder<StatusDisplayItem> holder){
if(holder instanceof ImageLoaderViewHolder){
int count=holder.getItem().getImageCount();
for(int i=0;i<count;i++){
((ImageLoaderViewHolder) holder).clearImage(i);
}
}
}
} }
} }

View File

@ -31,9 +31,9 @@ public class NotificationsFragment extends BaseStatusListFragment<Notification>{
case FAVORITE -> getString(R.string.user_favorited, n.account.displayName); case FAVORITE -> getString(R.string.user_favorited, n.account.displayName);
case POLL -> getString(R.string.poll_ended); case POLL -> getString(R.string.poll_ended);
case STATUS -> getString(R.string.user_posted, n.account.displayName); case STATUS -> getString(R.string.user_posted, n.account.displayName);
}); }, n.account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled);
if(n.status!=null){ if(n.status!=null){
ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n); ArrayList<StatusDisplayItem> items=StatusDisplayItem.buildItems(this, n.status, accountID, n, knownAccounts);
items.add(0, titleItem); items.add(0, titleItem);
return items; return items;
}else{ }else{
@ -41,6 +41,14 @@ public class NotificationsFragment extends BaseStatusListFragment<Notification>{
} }
} }
@Override
protected void addAccountToKnown(Notification s){
if(!knownAccounts.containsKey(s.account.id))
knownAccounts.put(s.account.id, s.account);
if(s.status!=null && !knownAccounts.containsKey(s.status.account.id))
knownAccounts.put(s.status.account.id, s.status.account);
}
@Override @Override
protected void doLoadData(int offset, int count){ protected void doLoadData(int offset, int count){
new GetNotifications(offset>0 ? getMaxID() : null, count) new GetNotifications(offset>0 ? getMaxID() : null, count)

View File

@ -16,7 +16,13 @@ import androidx.recyclerview.widget.RecyclerView;
public abstract class StatusListFragment extends BaseStatusListFragment<Status>{ public abstract class StatusListFragment extends BaseStatusListFragment<Status>{
protected List<StatusDisplayItem> buildDisplayItems(Status s){ protected List<StatusDisplayItem> buildDisplayItems(Status s){
return StatusDisplayItem.buildItems(this, s, accountID, s); return StatusDisplayItem.buildItems(this, s, accountID, s, knownAccounts);
}
@Override
protected void addAccountToKnown(Status s){
if(!knownAccounts.containsKey(s.account.id))
knownAccounts.put(s.account.id, s.account);
} }
@Override @Override

View File

@ -0,0 +1,44 @@
package org.joinmastodon.android.ui.displayitems;
import android.graphics.drawable.Drawable;
import android.text.Spanned;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
class CustomEmojiHelper{
public List<List<CustomEmojiSpan>> spans=new ArrayList<>();
public List<ImageLoaderRequest> requests=new ArrayList<>();
public void setText(CharSequence text){
spans.clear();
requests.clear();
if(!(text instanceof Spanned))
return;
CustomEmojiSpan[] spans=((Spanned) text).getSpans(0, text.length(), CustomEmojiSpan.class);
for(List<CustomEmojiSpan> group:Arrays.stream(spans).collect(Collectors.groupingBy(s->s.emoji)).values()){
this.spans.add(group);
requests.add(group.get(0).createImageLoaderRequest());
}
}
public int getImageCount(){
return requests.size();
}
public ImageLoaderRequest getImageRequest(int image){
return requests.get(image);
}
public void setImageDrawable(int image, Drawable drawable){
for(CustomEmojiSpan span:spans.get(image)){
span.setDrawable(drawable);
}
}
}

View File

@ -7,7 +7,6 @@ import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Bundle; import android.os.Bundle;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewOutlineProvider; import android.view.ViewOutlineProvider;
@ -18,7 +17,6 @@ import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.fragments.ProfileFragment; import org.joinmastodon.android.fragments.ProfileFragment;
import org.joinmastodon.android.model.Account; import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import org.parceler.Parcels; import org.parceler.Parcels;
@ -29,7 +27,6 @@ import me.grishka.appkit.Nav;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V; import me.grishka.appkit.utils.V;
public class HeaderStatusDisplayItem extends StatusDisplayItem{ public class HeaderStatusDisplayItem extends StatusDisplayItem{
@ -38,8 +35,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
private ImageLoaderRequest avaRequest; private ImageLoaderRequest avaRequest;
private Fragment parentFragment; private Fragment parentFragment;
private String accountID; private String accountID;
private ImageLoaderRequest[] emojiRequests; private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private CustomEmojiSpan[] emojiSpans;
private SpannableStringBuilder parsedName; private SpannableStringBuilder parsedName;
public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID){ public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID){
@ -51,11 +47,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
this.accountID=accountID; this.accountID=accountID;
parsedName=new SpannableStringBuilder(user.displayName); parsedName=new SpannableStringBuilder(user.displayName);
HtmlParser.parseCustomEmoji(parsedName, user.emojis); HtmlParser.parseCustomEmoji(parsedName, user.emojis);
emojiSpans=parsedName.getSpans(0, parsedName.length(), CustomEmojiSpan.class); emojiHelper.setText(parsedName);
emojiRequests=new ImageLoaderRequest[emojiSpans.length];
for(int i=0; i<emojiSpans.length; i++){
emojiRequests[i]=emojiSpans[i].createImageLoaderRequest();
}
} }
@Override @Override
@ -65,13 +57,13 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public int getImageCount(){ public int getImageCount(){
return 1+emojiRequests.length; return 1+emojiHelper.getImageCount();
} }
@Override @Override
public ImageLoaderRequest getImageRequest(int index){ public ImageLoaderRequest getImageRequest(int index){
if(index>0){ if(index>0){
return emojiRequests[index-1]; return emojiHelper.getImageRequest(index-1);
} }
return avaRequest; return avaRequest;
} }
@ -110,7 +102,7 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void setImage(int index, Drawable drawable){ public void setImage(int index, Drawable drawable){
if(index>0){ if(index>0){
item.emojiSpans[index-1].setDrawable(drawable); item.emojiHelper.setImageDrawable(index-1, drawable);
}else{ }else{
avatar.setImageDrawable(drawable); avatar.setImageDrawable(drawable);
} }

View File

@ -1,22 +1,37 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import android.app.Activity; import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Build; import android.os.Build;
import android.text.SpannableStringBuilder;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Emoji;
import org.joinmastodon.android.ui.text.HtmlParser;
import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.utils.UiUtils;
import me.grishka.appkit.utils.BindableViewHolder; import java.util.List;
import androidx.annotation.DrawableRes;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
private CharSequence text; private CharSequence text;
@DrawableRes
private int icon;
private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text){ public ReblogOrReplyLineStatusDisplayItem(String parentID, BaseStatusListFragment parentFragment, CharSequence text, List<Emoji> emojis, @DrawableRes int icon){
super(parentID, parentFragment); super(parentID, parentFragment);
this.text=text; SpannableStringBuilder ssb=new SpannableStringBuilder(text);
HtmlParser.parseCustomEmoji(ssb, emojis);
this.text=ssb;
emojiHelper.setText(ssb);
this.icon=icon;
} }
@Override @Override
@ -24,7 +39,17 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
return Type.REBLOG_OR_REPLY_LINE; return Type.REBLOG_OR_REPLY_LINE;
} }
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem>{ @Override
public int getImageCount(){
return emojiHelper.getImageCount();
}
@Override
public ImageLoaderRequest getImageRequest(int index){
return emojiHelper.getImageRequest(index);
}
public static class Holder extends StatusDisplayItem.Holder<ReblogOrReplyLineStatusDisplayItem> implements ImageLoaderViewHolder{
private final TextView text; private final TextView text;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_reblog_or_reply_line, parent); super(activity, R.layout.display_item_reblog_or_reply_line, parent);
@ -36,6 +61,17 @@ public class ReblogOrReplyLineStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(ReblogOrReplyLineStatusDisplayItem item){ public void onBind(ReblogOrReplyLineStatusDisplayItem item){
text.setText(item.text); text.setText(item.text);
text.setCompoundDrawablesRelativeWithIntrinsicBounds(item.icon, 0, 0, 0);
}
@Override
public void setImage(int index, Drawable image){
item.emojiHelper.setImageDrawable(index, image);
}
@Override
public void clearImage(int index){
setImage(index, null);
} }
} }
} }

View File

@ -1,7 +1,6 @@
package org.joinmastodon.android.ui.displayitems; package org.joinmastodon.android.ui.displayitems;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
@ -9,12 +8,15 @@ import android.view.ViewGroup;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.model.Account;
import org.joinmastodon.android.model.Attachment; import org.joinmastodon.android.model.Attachment;
import org.joinmastodon.android.model.DisplayItemsParent; import org.joinmastodon.android.model.DisplayItemsParent;
import org.joinmastodon.android.model.Status; import org.joinmastodon.android.model.Status;
import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.text.HtmlParser;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.BindableViewHolder;
@ -51,12 +53,15 @@ public abstract class StatusDisplayItem{
}; };
} }
public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject){ public static ArrayList<StatusDisplayItem> buildItems(BaseStatusListFragment fragment, Status status, String accountID, DisplayItemsParent parentObject, Map<String, Account> knownAccounts){
String parentID=parentObject.getID(); String parentID=parentObject.getID();
ArrayList<StatusDisplayItem> items=new ArrayList<>(); ArrayList<StatusDisplayItem> items=new ArrayList<>();
Status statusForContent=status.getContentStatus(); Status statusForContent=status.getContentStatus();
if(status.reblog!=null){ if(status.reblog!=null){
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.user_boosted, status.account.displayName))); 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));
}else if(status.inReplyToAccountId!=null && knownAccounts.containsKey(status.inReplyToAccountId)){
Account account=Objects.requireNonNull(knownAccounts.get(status.inReplyToAccountId));
items.add(new ReblogOrReplyLineStatusDisplayItem(parentID, fragment, fragment.getString(R.string.in_reply_to, account.displayName), account.emojis, R.drawable.ic_fluent_arrow_reply_20_filled));
} }
items.add(new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID)); items.add(new HeaderStatusDisplayItem(parentID, statusForContent.account, statusForContent.createdAt, fragment, accountID));
if(!TextUtils.isEmpty(statusForContent.content)) if(!TextUtils.isEmpty(statusForContent.content))

View File

@ -4,41 +4,25 @@ import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.Spanned;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import org.joinmastodon.android.R; import org.joinmastodon.android.R;
import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.BaseStatusListFragment;
import org.joinmastodon.android.ui.text.CustomEmojiSpan;
import org.joinmastodon.android.ui.text.LinkSpan;
import org.joinmastodon.android.ui.utils.UiUtils;
import org.joinmastodon.android.ui.views.LinkedTextView; import org.joinmastodon.android.ui.views.LinkedTextView;
import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ImageLoaderViewHolder;
import me.grishka.appkit.imageloader.MovieDrawable; import me.grishka.appkit.imageloader.MovieDrawable;
import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.imageloader.requests.ImageLoaderRequest;
import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest;
import me.grishka.appkit.utils.BindableViewHolder;
import me.grishka.appkit.utils.V;
public class TextStatusDisplayItem extends StatusDisplayItem{ public class TextStatusDisplayItem extends StatusDisplayItem{
private CharSequence text; private CharSequence text;
private ImageLoaderRequest[] emojiRequests; private CustomEmojiHelper emojiHelper=new CustomEmojiHelper();
private Fragment parentFragment; private Fragment parentFragment;
public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment){ public TextStatusDisplayItem(String parentID, CharSequence text, BaseStatusListFragment parentFragment){
super(parentID, parentFragment); super(parentID, parentFragment);
this.text=text; this.text=text;
this.parentFragment=parentFragment; this.parentFragment=parentFragment;
if(text instanceof Spanned){ emojiHelper.setText(text);
CustomEmojiSpan[] emojiSpans=((Spanned) text).getSpans(0, text.length(), CustomEmojiSpan.class);
emojiRequests=new ImageLoaderRequest[emojiSpans.length];
for(int i=0; i<emojiSpans.length; i++){
emojiRequests[i]=emojiSpans[i].createImageLoaderRequest();
}
}else{
emojiRequests=new ImageLoaderRequest[0];
}
} }
@Override @Override
@ -48,17 +32,16 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public int getImageCount(){ public int getImageCount(){
return emojiRequests.length; return emojiHelper.getImageCount();
} }
@Override @Override
public ImageLoaderRequest getImageRequest(int index){ public ImageLoaderRequest getImageRequest(int index){
return emojiRequests[index]; return emojiHelper.getImageRequest(index);
} }
public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{ public static class Holder extends StatusDisplayItem.Holder<TextStatusDisplayItem> implements ImageLoaderViewHolder{
private final LinkedTextView text; private final LinkedTextView text;
private CustomEmojiSpan[] emojiSpans;
public Holder(Activity activity, ViewGroup parent){ public Holder(Activity activity, ViewGroup parent){
super(activity, R.layout.display_item_text, parent); super(activity, R.layout.display_item_text, parent);
@ -68,21 +51,12 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void onBind(TextStatusDisplayItem item){ public void onBind(TextStatusDisplayItem item){
text.setText(item.text); text.setText(item.text);
if(emojiSpans!=null){
for(CustomEmojiSpan span:emojiSpans){
span.setDrawable(null);
}
}
if(item.text instanceof Spanned)
emojiSpans=((Spanned) item.text).getSpans(0, item.text.length(), CustomEmojiSpan.class);
else
emojiSpans=new CustomEmojiSpan[0];
text.setInvalidateOnEveryFrame(false); text.setInvalidateOnEveryFrame(false);
} }
@Override @Override
public void setImage(int index, Drawable image){ public void setImage(int index, Drawable image){
emojiSpans[index].setDrawable(image); item.emojiHelper.setImageDrawable(index, image);
text.invalidate(); text.invalidate();
if(image instanceof Animatable){ if(image instanceof Animatable){
((Animatable) image).start(); ((Animatable) image).start();
@ -93,7 +67,7 @@ public class TextStatusDisplayItem extends StatusDisplayItem{
@Override @Override
public void clearImage(int index){ public void clearImage(int index){
emojiSpans[index].setDrawable(null); item.emojiHelper.setImageDrawable(index, null);
text.invalidate(); text.invalidate();
} }
} }