Fix issue #288 - Custom Emoji not always displayed

maths
Thomas 2 years ago
parent ffb6596c99
commit 2cb08046f5

@ -115,6 +115,10 @@ public class Status implements Serializable, Cloneable {
public transient boolean submitted = false; public transient boolean submitted = false;
public transient boolean spoilerChecked = false; public transient boolean spoilerChecked = false;
public Filter filteredByApp; public Filter filteredByApp;
public transient Spannable contentSpan;
public transient Spannable contentSpoilerSpan;
public transient Spannable contentTranslateSpan;
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {
boolean same = false; boolean same = false;
@ -124,17 +128,29 @@ public class Status implements Serializable, Cloneable {
return same; return same;
} }
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference, Callback callback) {
return SpannableHelper.convert(context, content, this, null, null, true, viewWeakReference); if (contentSpan == null) {
contentSpan = SpannableHelper.convert(context, content, this, null, null, true, viewWeakReference, callback);
}
return contentSpan;
} }
public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference, Callback callback) {
if (contentSpoilerSpan == null) {
contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, true, viewWeakReference, callback);
}
return contentSpoilerSpan;
}
public synchronized Spannable getSpanSpoiler(Context context, WeakReference<View> viewWeakReference) { public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference, Callback callback) {
return SpannableHelper.convert(context, spoiler_text, this, null, null, true, viewWeakReference); if (contentTranslateSpan == null) {
contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, true, viewWeakReference, callback);
}
return contentTranslateSpan;
} }
public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference) { public interface Callback {
return SpannableHelper.convert(context, translationContent, this, null, null, true, viewWeakReference); void emojiFetched();
} }
@NonNull @NonNull

@ -6,6 +6,7 @@ import android.graphics.Canvas;
import android.graphics.Paint; import android.graphics.Paint;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.text.SpannableStringBuilder;
import android.text.style.ReplacementSpan; import android.text.style.ReplacementSpan;
import android.view.View; import android.view.View;
@ -13,26 +14,53 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.request.transition.Transition;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import app.fedilab.android.R; import app.fedilab.android.R;
import app.fedilab.android.client.entities.api.Emoji;
import app.fedilab.android.client.entities.api.Status;
public class CustomEmoji extends ReplacementSpan { public class CustomEmoji extends ReplacementSpan {
private final float scale; private final float scale;
private final WeakReference<View> viewWeakReference; private final WeakReference<View> viewWeakReference;
private Drawable imageDrawable; private Drawable imageDrawable;
private boolean callbackCalled;
CustomEmoji(WeakReference<View> viewWeakReference) { CustomEmoji(WeakReference<View> viewWeakReference) {
Context mContext = viewWeakReference.get().getContext(); Context mContext = viewWeakReference.get().getContext();
this.viewWeakReference = viewWeakReference; this.viewWeakReference = viewWeakReference;
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(mContext); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
scale = sharedpreferences.getFloat(mContext.getString(R.string.SET_FONT_SCALE), 1.1f); scale = sharedpreferences.getFloat(mContext.getString(R.string.SET_FONT_SCALE), 1.1f);
callbackCalled = false;
}
public SpannableStringBuilder makeEmoji(SpannableStringBuilder content, List<Emoji> emojiList, boolean animate, Status.Callback callback) {
if (emojiList != null && emojiList.size() > 0) {
int count = 1;
for (Emoji emoji : emojiList) {
Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
.matcher(content);
while (matcher.find()) {
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(viewWeakReference.get()));
content.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
Glide.with(viewWeakReference.get())
.asDrawable()
.load(animate ? emoji.url : emoji.static_url)
.into(customEmoji.getTarget(animate, count == emojiList.size() && !callbackCalled ? callback : null));
}
count++;
}
}
return content;
} }
@Override @Override
@ -61,7 +89,7 @@ public class CustomEmoji extends ReplacementSpan {
} }
} }
public Target<Drawable> getTarget(boolean animate) { public Target<Drawable> getTarget(boolean animate, Status.Callback callback) {
return new CustomTarget<Drawable>() { return new CustomTarget<Drawable>() {
@Override @Override
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) { public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
@ -97,6 +125,10 @@ public class CustomEmoji extends ReplacementSpan {
if (view != null) { if (view != null) {
view.invalidate(); view.invalidate();
} }
if (callback != null) {
callbackCalled = true;
callback.emojiFetched();
}
} }
@Override @Override

@ -89,10 +89,16 @@ public class SpannableHelper {
public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN"; public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN";
public static Spannable convert(Context context, String text,
Status status, Account account, Announcement announcement,
boolean convertHtml, WeakReference<View> viewWeakReference) {
return convert(context, text, status, account, announcement, convertHtml, viewWeakReference, null);
}
public static Spannable convert(Context context, String text, public static Spannable convert(Context context, String text,
Status status, Account account, Announcement announcement, Status status, Account account, Announcement announcement,
boolean convertHtml, boolean convertHtml,
WeakReference<View> viewWeakReference) { WeakReference<View> viewWeakReference, Status.Callback callback) {
SpannableString initialContent; SpannableString initialContent;
@ -161,20 +167,8 @@ public class SpannableHelper {
} }
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
boolean animate = !sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false); boolean animate = !sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
if (emojiList != null && emojiList.size() > 0) { CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
for (Emoji emoji : emojiList) { content = customEmoji.makeEmoji(content, emojiList, animate, callback);
Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL)
.matcher(content);
while (matcher.find()) {
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
content.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
Glide.with(view)
.asDrawable()
.load(animate ? emoji.url : emoji.static_url)
.into(customEmoji.getTarget(animate));
}
}
}
if (imagesToReplace.size() > 0) { if (imagesToReplace.size() > 0) {
for (Map.Entry<String, String> entry : imagesToReplace.entrySet()) { for (Map.Entry<String, String> entry : imagesToReplace.entrySet()) {
@ -183,12 +177,11 @@ public class SpannableHelper {
Matcher matcher = Pattern.compile(key, Pattern.LITERAL) Matcher matcher = Pattern.compile(key, Pattern.LITERAL)
.matcher(content); .matcher(content);
while (matcher.find()) { while (matcher.find()) {
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
content.setSpan(customEmoji, matcher.start(), matcher.end(), 0); content.setSpan(customEmoji, matcher.start(), matcher.end(), 0);
Glide.with(view) Glide.with(view)
.asDrawable() .asDrawable()
.load(url) .load(url)
.into(customEmoji.getTarget(animate)); .into(customEmoji.getTarget(animate, null));
} }
} }
@ -1066,7 +1059,7 @@ public class SpannableHelper {
Glide.with(viewWeakReference.get()) Glide.with(viewWeakReference.get())
.asDrawable() .asDrawable()
.load(animate ? emoji.url : emoji.static_url) .load(animate ? emoji.url : emoji.static_url)
.into(customEmoji.getTarget(animate)); .into(customEmoji.getTarget(animate, null));
} }
} }
} }

@ -1107,7 +1107,7 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
StatusSimpleViewHolder holder = (StatusSimpleViewHolder) viewHolder; StatusSimpleViewHolder holder = (StatusSimpleViewHolder) viewHolder;
holder.binding.statusContent.setText( holder.binding.statusContent.setText(
status.getSpanContent(context, status.getSpanContent(context,
new WeakReference<>(holder.binding.statusContent)), new WeakReference<>(holder.binding.statusContent), null),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
MastodonHelper.loadPPMastodon(holder.binding.avatar, status.account); MastodonHelper.loadPPMastodon(holder.binding.avatar, status.account);
if (status.account != null) { if (status.account != null) {
@ -1122,7 +1122,7 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
holder.binding.spoiler.setVisibility(View.VISIBLE); holder.binding.spoiler.setVisibility(View.VISIBLE);
holder.binding.spoiler.setText( holder.binding.spoiler.setText(
status.getSpanSpoiler(context, status.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)), new WeakReference<>(holder.binding.spoiler), null),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} else { } else {
holder.binding.spoiler.setVisibility(View.GONE); holder.binding.spoiler.setVisibility(View.GONE);

@ -151,7 +151,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
holder.binding.spoiler.setVisibility(View.VISIBLE); holder.binding.spoiler.setVisibility(View.VISIBLE);
holder.binding.spoiler.setText( holder.binding.spoiler.setText(
conversation.last_status.getSpanSpoiler(context, conversation.last_status.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)), new WeakReference<>(holder.binding.spoiler), () -> notifyItemChanged(holder.getBindingAdapterPosition())),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} else { } else {
holder.binding.spoiler.setVisibility(View.GONE); holder.binding.spoiler.setVisibility(View.GONE);
@ -161,7 +161,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
//--- MAIN CONTENT --- //--- MAIN CONTENT ---
holder.binding.statusContent.setText( holder.binding.statusContent.setText(
conversation.last_status.getSpanContent(context, conversation.last_status.getSpanContent(context,
new WeakReference<>(holder.binding.statusContent)), new WeakReference<>(holder.binding.statusContent), () -> notifyItemChanged(holder.getBindingAdapterPosition())),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
//--- DATE --- //--- DATE ---
holder.binding.lastMessageDate.setText(Helper.dateDiff(context, conversation.last_status.created_at)); holder.binding.lastMessageDate.setText(Helper.dateDiff(context, conversation.last_status.created_at));

@ -71,6 +71,7 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
private final int TYPE_ADMIN_REPORT = 12; private final int TYPE_ADMIN_REPORT = 12;
public FetchMoreCallBack fetchMoreCallBack; public FetchMoreCallBack fetchMoreCallBack;
private Context context; private Context context;
private RecyclerView mRecyclerView;
public NotificationAdapter(List<Notification> notificationList) { public NotificationAdapter(List<Notification> notificationList) {
this.notificationList = notificationList; this.notificationList = notificationList;
@ -120,6 +121,13 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
return super.getItemViewType(position); return super.getItemViewType(position);
} }
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
@NonNull @NonNull
@Override @Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
@ -249,7 +257,7 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
if (notification.status != null) { if (notification.status != null) {
notification.status.cached = notification.cached; notification.status.cached = notification.cached;
} }
statusManagement(context, statusesVM, searchVM, holderStatus, this, null, notification.status, Timeline.TimeLineEnum.NOTIFICATION, false, true, false, null); statusManagement(context, statusesVM, searchVM, holderStatus, mRecyclerView, this, null, notification.status, Timeline.TimeLineEnum.NOTIFICATION, false, true, false, null);
holderStatus.bindingNotification.status.dateShort.setText(Helper.dateDiff(context, notification.created_at)); holderStatus.bindingNotification.status.dateShort.setText(Helper.dateDiff(context, notification.created_at));
if (getItemViewType(position) == TYPE_MENTION || getItemViewType(position) == TYPE_STATUS || getItemViewType(position) == TYPE_REACTION) { if (getItemViewType(position) == TYPE_MENTION || getItemViewType(position) == TYPE_STATUS || getItemViewType(position) == TYPE_REACTION) {

@ -165,6 +165,8 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public FetchMoreCallBack fetchMoreCallBack; public FetchMoreCallBack fetchMoreCallBack;
private Context context; private Context context;
private RecyclerView mRecyclerView;
public StatusAdapter(List<Status> statuses, Timeline.TimeLineEnum timelineType, boolean minified, boolean canBeFederated, boolean checkRemotely) { public StatusAdapter(List<Status> statuses, Timeline.TimeLineEnum timelineType, boolean minified, boolean canBeFederated, boolean checkRemotely) {
this.statusList = statuses; this.statusList = statuses;
this.timelineType = timelineType; this.timelineType = timelineType;
@ -355,6 +357,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
StatusesVM statusesVM, StatusesVM statusesVM,
SearchVM searchVM, SearchVM searchVM,
StatusViewHolder holder, StatusViewHolder holder,
RecyclerView recyclerView,
RecyclerView.Adapter<RecyclerView.ViewHolder> adapter, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter,
List<Status> statusList, List<Status> statusList,
Status status, Status status,
@ -986,7 +989,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.spoiler.setVisibility(View.VISIBLE); holder.binding.spoiler.setVisibility(View.VISIBLE);
holder.binding.spoiler.setText( holder.binding.spoiler.setText(
statusToDeal.getSpanSpoiler(context, statusToDeal.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)), new WeakReference<>(holder.binding.spoiler), () -> {
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
}),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
statusToDeal.isExpended = true; statusToDeal.isExpended = true;
statusToDeal.isMediaDisplayed = true; statusToDeal.isMediaDisplayed = true;
@ -1001,7 +1006,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.spoiler.setText( holder.binding.spoiler.setText(
statusToDeal.getSpanSpoiler(context, statusToDeal.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)), new WeakReference<>(holder.binding.spoiler), () -> {
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
}),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} }
if (statusToDeal.isExpended) { if (statusToDeal.isExpended) {
@ -1049,7 +1056,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
//--- MAIN CONTENT --- //--- MAIN CONTENT ---
holder.binding.statusContent.setText( holder.binding.statusContent.setText(
statusToDeal.getSpanContent(context, statusToDeal.getSpanContent(context,
new WeakReference<>(holder.binding.statusContent)), new WeakReference<>(holder.binding.statusContent), () -> {
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
}),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
if (truncate_toots_size > 0) { if (truncate_toots_size > 0) {
holder.binding.statusContent.setMaxLines(truncate_toots_size); holder.binding.statusContent.setMaxLines(truncate_toots_size);
@ -1085,7 +1094,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.containerTrans.setVisibility(View.VISIBLE); holder.binding.containerTrans.setVisibility(View.VISIBLE);
holder.binding.statusContentTranslated.setText( holder.binding.statusContentTranslated.setText(
statusToDeal.getSpanTranslate(context, statusToDeal.getSpanTranslate(context,
new WeakReference<>(holder.binding.statusContentTranslated)), new WeakReference<>(holder.binding.statusContentTranslated), () -> {
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
}),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} else { } else {
holder.binding.containerTrans.setVisibility(View.GONE); holder.binding.containerTrans.setVisibility(View.GONE);
@ -2087,6 +2098,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
} }
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
mRecyclerView = recyclerView;
}
private static boolean mediaObfuscated(Status status) { private static boolean mediaObfuscated(Status status) {
//Media is not sensitive and doesn't have a spoiler text //Media is not sensitive and doesn't have a spoiler text
if (!status.isMediaObfuscated) { if (!status.isMediaObfuscated) {
@ -2200,7 +2218,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class); StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.class);
SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class); SearchVM searchVM = new ViewModelProvider((ViewModelStoreOwner) context).get(SearchVM.class);
statusManagement(context, statusesVM, searchVM, holder, this, statusList, status, timelineType, minified, canBeFederated, checkRemotely, fetchMoreCallBack); statusManagement(context, statusesVM, searchVM, holder, mRecyclerView, this, statusList, status, timelineType, minified, canBeFederated, checkRemotely, fetchMoreCallBack);
} else if (viewHolder.getItemViewType() == STATUS_FILTERED_HIDE) { } else if (viewHolder.getItemViewType() == STATUS_FILTERED_HIDE) {
StatusViewHolder holder = (StatusViewHolder) viewHolder; StatusViewHolder holder = (StatusViewHolder) viewHolder;

@ -56,13 +56,13 @@ public class StatusHistoryAdapter extends RecyclerView.Adapter<RecyclerView.View
Status status = statuses.get(position); Status status = statuses.get(position);
holder.binding.statusContent.setText( holder.binding.statusContent.setText(
status.getSpanContent(context, status.getSpanContent(context,
new WeakReference<>(holder.binding.statusContent)), new WeakReference<>(holder.binding.statusContent), null),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
if (status.spoiler_text != null && !status.spoiler_text.trim().isEmpty()) { if (status.spoiler_text != null && !status.spoiler_text.trim().isEmpty()) {
holder.binding.spoiler.setVisibility(View.VISIBLE); holder.binding.spoiler.setVisibility(View.VISIBLE);
holder.binding.spoiler.setText( holder.binding.spoiler.setText(
status.getSpanSpoiler(context, status.getSpanSpoiler(context,
new WeakReference<>(holder.binding.spoiler)), new WeakReference<>(holder.binding.spoiler), null),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} else { } else {
holder.binding.spoiler.setVisibility(View.GONE); holder.binding.spoiler.setVisibility(View.GONE);

Loading…
Cancel
Save