From c9078ca8d71004a9ac955596117f01d60aa14758 Mon Sep 17 00:00:00 2001 From: Grishka Date: Thu, 10 Feb 2022 22:10:16 +0300 Subject: [PATCH] Custom emoji in more places --- .../fragments/ProfileAboutFragment.java | 39 +++++++++++++++-- .../android/fragments/ProfileFragment.java | 26 +++++++++-- .../android/model/AccountField.java | 9 +++- .../displayitems/HeaderStatusDisplayItem.java | 29 +++++++++++-- .../displayitems/TextStatusDisplayItem.java | 3 +- .../android/ui/text/CustomEmojiSpan.java | 7 +++ .../android/ui/utils/UiUtils.java | 43 ++++++++++++++++++- 7 files changed, 140 insertions(+), 16 deletions(-) diff --git a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileAboutFragment.java b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileAboutFragment.java index c8ca4835..80883ec3 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileAboutFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileAboutFragment.java @@ -5,6 +5,7 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Outline; import android.graphics.Paint; +import android.graphics.drawable.Drawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.os.Bundle; @@ -17,6 +18,7 @@ import android.widget.TextView; import org.joinmastodon.android.R; import org.joinmastodon.android.model.AccountField; +import org.joinmastodon.android.ui.text.CustomEmojiSpan; import org.joinmastodon.android.ui.utils.SimpleTextWatcher; import org.joinmastodon.android.ui.views.LinkedTextView; @@ -28,7 +30,11 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import me.grishka.appkit.imageloader.ImageLoaderRecyclerAdapter; +import me.grishka.appkit.imageloader.ImageLoaderViewHolder; import me.grishka.appkit.imageloader.ListImageLoaderWrapper; +import me.grishka.appkit.imageloader.RecyclerViewDelegate; +import me.grishka.appkit.imageloader.requests.ImageLoaderRequest; import me.grishka.appkit.utils.BindableViewHolder; import me.grishka.appkit.utils.CubicBezierInterpolator; import me.grishka.appkit.utils.V; @@ -44,6 +50,7 @@ public class ProfileAboutFragment extends Fragment{ private boolean isInEditMode; private ItemTouchHelper dragHelper=new ItemTouchHelper(new ReorderCallback()); private RecyclerView.ViewHolder draggedViewHolder; + private ListImageLoaderWrapper imgLoader; public void setFields(List fields){ this.fields=fields; @@ -61,6 +68,7 @@ public class ProfileAboutFragment extends Fragment{ list=new UsableRecyclerView(getActivity()); list.setId(R.id.list); list.setLayoutManager(new LinearLayoutManager(getActivity())); + imgLoader=new ListImageLoaderWrapper(getActivity(), list, new RecyclerViewDelegate(list), null); list.setAdapter(adapter=new AboutAdapter()); int pad=V.dp(16); list.setPadding(pad, pad, pad, pad); @@ -95,9 +103,9 @@ public class ProfileAboutFragment extends Fragment{ return fields; } - private class AboutAdapter extends UsableRecyclerView.Adapter{ + private class AboutAdapter extends UsableRecyclerView.Adapter implements ImageLoaderRecyclerAdapter{ public AboutAdapter(){ - super(null); + super(imgLoader); } @NonNull @@ -139,6 +147,16 @@ public class ProfileAboutFragment extends Fragment{ } return 0; } + + @Override + public int getImageCountForItem(int position){ + return isInEditMode || fields.get(position).emojiRequests==null ? 0 : fields.get(position).emojiRequests.size(); + } + + @Override + public ImageLoaderRequest getImageRequest(int position, int image){ + return fields.get(position).emojiRequests.get(image); + } } private abstract class BaseViewHolder extends BindableViewHolder{ @@ -164,7 +182,7 @@ public class ProfileAboutFragment extends Fragment{ } } - private class AboutViewHolder extends BaseViewHolder{ + private class AboutViewHolder extends BaseViewHolder implements ImageLoaderViewHolder{ private TextView title; private LinkedTextView value; @@ -177,9 +195,22 @@ public class ProfileAboutFragment extends Fragment{ @Override public void onBind(AccountField item){ super.onBind(item); - title.setText(item.name); + title.setText(item.parsedName); value.setText(item.parsedValue); } + + @Override + public void setImage(int index, Drawable image){ + CustomEmojiSpan span=index>=item.nameEmojis.length ? item.valueEmojis[index-item.nameEmojis.length] : item.nameEmojis[index]; + span.setDrawable(image); + title.invalidate(); + value.invalidate(); + } + + @Override + public void clearImage(int index){ + setImage(index, null); + } } private class EditableAboutViewHolder extends BaseViewHolder{ 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 b8b26134..ab0ec7b5 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java +++ b/mastodon/src/main/java/org/joinmastodon/android/fragments/ProfileFragment.java @@ -12,6 +12,7 @@ import android.graphics.Outline; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.text.SpannableStringBuilder; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -42,6 +43,7 @@ import org.joinmastodon.android.model.Relationship; import org.joinmastodon.android.ui.drawables.CoverOverlayGradientDrawable; import org.joinmastodon.android.ui.tabs.TabLayout; import org.joinmastodon.android.ui.tabs.TabLayoutMediator; +import org.joinmastodon.android.ui.text.CustomEmojiSpan; import org.joinmastodon.android.ui.text.HtmlParser; import org.joinmastodon.android.ui.utils.UiUtils; import org.joinmastodon.android.ui.views.CoverImageView; @@ -266,7 +268,10 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList setSubtitle(getResources().getQuantityString(R.plurals.x_posts, account.statusesCount, account.statusesCount)); ViewImageLoader.load(avatar, null, new UrlImageLoaderRequest(account.avatar, V.dp(100), V.dp(100))); ViewImageLoader.load(cover, null, new UrlImageLoaderRequest(account.header, 1000, 1000)); - name.setText(account.displayName); + SpannableStringBuilder ssb=new SpannableStringBuilder(account.displayName); + HtmlParser.parseCustomEmoji(ssb, account.emojis); + name.setText(ssb); + setTitle(ssb); username.setText('@'+account.acct); bio.setText(HtmlParser.parse(account.note, account.emojis)); followersCount.setText(UiUtils.abbreviateNumber(account.followersCount)); @@ -276,6 +281,9 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList followingLabel.setText(getResources().getQuantityString(R.plurals.following, account.followingCount)); postsLabel.setText(getResources().getQuantityString(R.plurals.posts, account.statusesCount)); + UiUtils.loadCustomEmojiInTextView(name); + UiUtils.loadCustomEmojiInTextView(bio); + if(AccountSessionManager.getInstance().isSelf(accountID, account)){ actionButton.setText(R.string.edit_profile); }else{ @@ -285,12 +293,24 @@ public class ProfileFragment extends LoaderFragment implements OnBackPressedList fields.clear(); AccountField joined=new AccountField(); - joined.name=getString(R.string.profile_joined); + joined.parsedName=joined.name=getString(R.string.profile_joined); joined.parsedValue=joined.value=DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).format(LocalDateTime.ofInstant(account.createdAt, ZoneId.systemDefault())); fields.add(joined); for(AccountField field:account.fields){ - field.parsedValue=HtmlParser.parse(field.value, account.emojis); + field.parsedValue=ssb=HtmlParser.parse(field.value, account.emojis); + field.valueEmojis=ssb.getSpans(0, ssb.length(), CustomEmojiSpan.class); + ssb=new SpannableStringBuilder(field.name); + HtmlParser.parseCustomEmoji(ssb, account.emojis); + field.parsedName=ssb; + field.nameEmojis=ssb.getSpans(0, ssb.length(), CustomEmojiSpan.class); + field.emojiRequests=new ArrayList<>(field.nameEmojis.length+field.valueEmojis.length); + for(CustomEmojiSpan span:field.nameEmojis){ + field.emojiRequests.add(span.createImageLoaderRequest()); + } + for(CustomEmojiSpan span:field.valueEmojis){ + field.emojiRequests.add(span.createImageLoaderRequest()); + } fields.add(field); } diff --git a/mastodon/src/main/java/org/joinmastodon/android/model/AccountField.java b/mastodon/src/main/java/org/joinmastodon/android/model/AccountField.java index 4d9f2661..5709ca6b 100644 --- a/mastodon/src/main/java/org/joinmastodon/android/model/AccountField.java +++ b/mastodon/src/main/java/org/joinmastodon/android/model/AccountField.java @@ -1,10 +1,13 @@ package org.joinmastodon.android.model; import org.joinmastodon.android.api.RequiredField; +import org.joinmastodon.android.ui.text.CustomEmojiSpan; import org.parceler.Parcel; -import org.parceler.Transient; import java.time.Instant; +import java.util.ArrayList; + +import me.grishka.appkit.imageloader.requests.UrlImageLoaderRequest; /** * Represents a profile field as a name-value pair with optional verification. @@ -26,7 +29,9 @@ public class AccountField extends BaseModel{ */ public Instant verifiedAt; - public transient CharSequence parsedValue; + public transient CharSequence parsedValue, parsedName; + public transient CustomEmojiSpan[] valueEmojis, nameEmojis; + public transient ArrayList emojiRequests; @Override public String toString(){ 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 741fdb36..b8839db3 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 @@ -6,6 +6,8 @@ import android.graphics.Outline; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.text.SpannableStringBuilder; +import android.text.Spanned; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; @@ -16,6 +18,8 @@ import org.joinmastodon.android.R; import org.joinmastodon.android.fragments.BaseStatusListFragment; import org.joinmastodon.android.fragments.ProfileFragment; 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.utils.UiUtils; import org.parceler.Parcels; @@ -34,6 +38,9 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ private ImageLoaderRequest avaRequest; private Fragment parentFragment; private String accountID; + private ImageLoaderRequest[] emojiRequests; + private CustomEmojiSpan[] emojiSpans; + private SpannableStringBuilder parsedName; public HeaderStatusDisplayItem(String parentID, Account user, Instant createdAt, BaseStatusListFragment parentFragment, String accountID){ super(parentID, parentFragment); @@ -42,6 +49,13 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ avaRequest=new UrlImageLoaderRequest(user.avatar); this.parentFragment=parentFragment; this.accountID=accountID; + parsedName=new SpannableStringBuilder(user.displayName); + HtmlParser.parseCustomEmoji(parsedName, user.emojis); + emojiSpans=parsedName.getSpans(0, parsedName.length(), CustomEmojiSpan.class); + emojiRequests=new ImageLoaderRequest[emojiSpans.length]; + for(int i=0; i0){ + return emojiRequests[index-1]; + } return avaRequest; } @@ -85,21 +102,25 @@ public class HeaderStatusDisplayItem extends StatusDisplayItem{ @Override public void onBind(HeaderStatusDisplayItem item){ - name.setText(item.user.displayName); + name.setText(item.parsedName); username.setText('@'+item.user.acct); timestamp.setText(UiUtils.formatRelativeTimestamp(itemView.getContext(), item.createdAt)); } @Override public void setImage(int index, Drawable drawable){ - avatar.setImageDrawable(drawable); + if(index>0){ + item.emojiSpans[index-1].setDrawable(drawable); + }else{ + avatar.setImageDrawable(drawable); + } if(drawable instanceof Animatable) ((Animatable) drawable).start(); } @Override public void clearImage(int index){ - avatar.setImageBitmap(null); + setImage(index, null); } private void onAvaClick(View v){ 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 dbc1eefa..33ef8b67 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 @@ -33,9 +33,8 @@ public class TextStatusDisplayItem extends StatusDisplayItem{ if(text instanceof Spanned){ CustomEmojiSpan[] emojiSpans=((Spanned) text).getSpans(0, text.length(), CustomEmojiSpan.class); emojiRequests=new ImageLoaderRequest[emojiSpans.length]; - int emojiSize=V.dp(20); for(int i=0; i> spansByEmoji=Arrays.stream(spans).collect(Collectors.groupingBy(s->s.emoji)); + for(Map.Entry> emoji:spansByEmoji.entrySet()){ + ViewImageLoader.load(new ViewImageLoader.Target(){ + @Override + public void setImageDrawable(Drawable d){ + if(d==null) + return; + for(CustomEmojiSpan span:emoji.getValue()){ + span.setDrawable(d); + } + view.invalidate(); + } + + @Override + public View getView(){ + return view; + } + }, null, new UrlImageLoaderRequest(emoji.getKey().url, emojiSize, emojiSize), null, false, true); + } + } }