Fix issue #288 - Custom Emoji not always displayed
This commit is contained in:
parent
ffb6596c99
commit
2cb08046f5
|
@ -115,6 +115,10 @@ public class Status implements Serializable, Cloneable {
|
|||
public transient boolean submitted = false;
|
||||
public transient boolean spoilerChecked = false;
|
||||
public Filter filteredByApp;
|
||||
public transient Spannable contentSpan;
|
||||
public transient Spannable contentSpoilerSpan;
|
||||
public transient Spannable contentTranslateSpan;
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
boolean same = false;
|
||||
|
@ -124,17 +128,29 @@ public class Status implements Serializable, Cloneable {
|
|||
return same;
|
||||
}
|
||||
|
||||
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference) {
|
||||
return SpannableHelper.convert(context, content, this, null, null, true, viewWeakReference);
|
||||
public synchronized Spannable getSpanContent(Context context, WeakReference<View> viewWeakReference, Callback callback) {
|
||||
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) {
|
||||
return SpannableHelper.convert(context, spoiler_text, this, null, null, true, viewWeakReference);
|
||||
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 getSpanTranslate(Context context, WeakReference<View> viewWeakReference) {
|
||||
return SpannableHelper.convert(context, translationContent, this, null, null, true, viewWeakReference);
|
||||
public synchronized Spannable getSpanTranslate(Context context, WeakReference<View> viewWeakReference, Callback callback) {
|
||||
if (contentTranslateSpan == null) {
|
||||
contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, true, viewWeakReference, callback);
|
||||
}
|
||||
return contentTranslateSpan;
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
void emojiFetched();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.graphics.Canvas;
|
|||
import android.graphics.Paint;
|
||||
import android.graphics.drawable.Animatable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ReplacementSpan;
|
||||
import android.view.View;
|
||||
|
||||
|
@ -13,26 +14,53 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
import com.bumptech.glide.request.transition.Transition;
|
||||
|
||||
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.client.entities.api.Emoji;
|
||||
import app.fedilab.android.client.entities.api.Status;
|
||||
|
||||
|
||||
public class CustomEmoji extends ReplacementSpan {
|
||||
private final float scale;
|
||||
private final WeakReference<View> viewWeakReference;
|
||||
private Drawable imageDrawable;
|
||||
|
||||
private boolean callbackCalled;
|
||||
|
||||
CustomEmoji(WeakReference<View> viewWeakReference) {
|
||||
Context mContext = viewWeakReference.get().getContext();
|
||||
this.viewWeakReference = viewWeakReference;
|
||||
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
|
||||
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
|
||||
|
@ -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>() {
|
||||
@Override
|
||||
public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
|
||||
|
@ -97,6 +125,10 @@ public class CustomEmoji extends ReplacementSpan {
|
|||
if (view != null) {
|
||||
view.invalidate();
|
||||
}
|
||||
if (callback != null) {
|
||||
callbackCalled = true;
|
||||
callback.emojiFetched();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -89,10 +89,16 @@ public class SpannableHelper {
|
|||
|
||||
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,
|
||||
Status status, Account account, Announcement announcement,
|
||||
boolean convertHtml,
|
||||
WeakReference<View> viewWeakReference) {
|
||||
WeakReference<View> viewWeakReference, Status.Callback callback) {
|
||||
|
||||
|
||||
SpannableString initialContent;
|
||||
|
@ -161,20 +167,8 @@ public class SpannableHelper {
|
|||
}
|
||||
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
boolean animate = !sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false);
|
||||
if (emojiList != null && emojiList.size() > 0) {
|
||||
for (Emoji emoji : emojiList) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
CustomEmoji customEmoji = new CustomEmoji(new WeakReference<>(view));
|
||||
content = customEmoji.makeEmoji(content, emojiList, animate, callback);
|
||||
|
||||
if (imagesToReplace.size() > 0) {
|
||||
for (Map.Entry<String, String> entry : imagesToReplace.entrySet()) {
|
||||
|
@ -183,12 +177,11 @@ public class SpannableHelper {
|
|||
Matcher matcher = Pattern.compile(key, 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(url)
|
||||
.into(customEmoji.getTarget(animate));
|
||||
.into(customEmoji.getTarget(animate, null));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1066,7 +1059,7 @@ public class SpannableHelper {
|
|||
Glide.with(viewWeakReference.get())
|
||||
.asDrawable()
|
||||
.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;
|
||||
holder.binding.statusContent.setText(
|
||||
status.getSpanContent(context,
|
||||
new WeakReference<>(holder.binding.statusContent)),
|
||||
new WeakReference<>(holder.binding.statusContent), null),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
MastodonHelper.loadPPMastodon(holder.binding.avatar, status.account);
|
||||
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.setText(
|
||||
status.getSpanSpoiler(context,
|
||||
new WeakReference<>(holder.binding.spoiler)),
|
||||
new WeakReference<>(holder.binding.spoiler), null),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
} else {
|
||||
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.setText(
|
||||
conversation.last_status.getSpanSpoiler(context,
|
||||
new WeakReference<>(holder.binding.spoiler)),
|
||||
new WeakReference<>(holder.binding.spoiler), () -> notifyItemChanged(holder.getBindingAdapterPosition())),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
} else {
|
||||
holder.binding.spoiler.setVisibility(View.GONE);
|
||||
|
@ -161,7 +161,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
//--- MAIN CONTENT ---
|
||||
holder.binding.statusContent.setText(
|
||||
conversation.last_status.getSpanContent(context,
|
||||
new WeakReference<>(holder.binding.statusContent)),
|
||||
new WeakReference<>(holder.binding.statusContent), () -> notifyItemChanged(holder.getBindingAdapterPosition())),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
//--- DATE ---
|
||||
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;
|
||||
public FetchMoreCallBack fetchMoreCallBack;
|
||||
private Context context;
|
||||
private RecyclerView mRecyclerView;
|
||||
|
||||
public NotificationAdapter(List<Notification> notificationList) {
|
||||
this.notificationList = notificationList;
|
||||
|
@ -120,6 +121,13 @@ public class NotificationAdapter extends RecyclerView.Adapter<RecyclerView.ViewH
|
|||
return super.getItemViewType(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
|
||||
super.onAttachedToRecyclerView(recyclerView);
|
||||
|
||||
mRecyclerView = recyclerView;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
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) {
|
||||
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));
|
||||
|
||||
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;
|
||||
private Context context;
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
|
||||
public StatusAdapter(List<Status> statuses, Timeline.TimeLineEnum timelineType, boolean minified, boolean canBeFederated, boolean checkRemotely) {
|
||||
this.statusList = statuses;
|
||||
this.timelineType = timelineType;
|
||||
|
@ -355,6 +357,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
StatusesVM statusesVM,
|
||||
SearchVM searchVM,
|
||||
StatusViewHolder holder,
|
||||
RecyclerView recyclerView,
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder> adapter,
|
||||
List<Status> statusList,
|
||||
Status status,
|
||||
|
@ -986,7 +989,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
holder.binding.spoiler.setVisibility(View.VISIBLE);
|
||||
holder.binding.spoiler.setText(
|
||||
statusToDeal.getSpanSpoiler(context,
|
||||
new WeakReference<>(holder.binding.spoiler)),
|
||||
new WeakReference<>(holder.binding.spoiler), () -> {
|
||||
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
|
||||
}),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
statusToDeal.isExpended = true;
|
||||
statusToDeal.isMediaDisplayed = true;
|
||||
|
@ -1001,7 +1006,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
|
||||
holder.binding.spoiler.setText(
|
||||
statusToDeal.getSpanSpoiler(context,
|
||||
new WeakReference<>(holder.binding.spoiler)),
|
||||
new WeakReference<>(holder.binding.spoiler), () -> {
|
||||
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
|
||||
}),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
}
|
||||
if (statusToDeal.isExpended) {
|
||||
|
@ -1049,7 +1056,9 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
//--- MAIN CONTENT ---
|
||||
holder.binding.statusContent.setText(
|
||||
statusToDeal.getSpanContent(context,
|
||||
new WeakReference<>(holder.binding.statusContent)),
|
||||
new WeakReference<>(holder.binding.statusContent), () -> {
|
||||
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
|
||||
}),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
if (truncate_toots_size > 0) {
|
||||
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.statusContentTranslated.setText(
|
||||
statusToDeal.getSpanTranslate(context,
|
||||
new WeakReference<>(holder.binding.statusContentTranslated)),
|
||||
new WeakReference<>(holder.binding.statusContentTranslated), () -> {
|
||||
recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()));
|
||||
}),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
} else {
|
||||
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) {
|
||||
//Media is not sensitive and doesn't have a spoiler text
|
||||
if (!status.isMediaObfuscated) {
|
||||
|
@ -2200,7 +2218,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
|
|||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
StatusesVM statusesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(StatusesVM.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) {
|
||||
StatusViewHolder holder = (StatusViewHolder) viewHolder;
|
||||
|
||||
|
|
|
@ -56,13 +56,13 @@ public class StatusHistoryAdapter extends RecyclerView.Adapter<RecyclerView.View
|
|||
Status status = statuses.get(position);
|
||||
holder.binding.statusContent.setText(
|
||||
status.getSpanContent(context,
|
||||
new WeakReference<>(holder.binding.statusContent)),
|
||||
new WeakReference<>(holder.binding.statusContent), null),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
if (status.spoiler_text != null && !status.spoiler_text.trim().isEmpty()) {
|
||||
holder.binding.spoiler.setVisibility(View.VISIBLE);
|
||||
holder.binding.spoiler.setText(
|
||||
status.getSpanSpoiler(context,
|
||||
new WeakReference<>(holder.binding.spoiler)),
|
||||
new WeakReference<>(holder.binding.spoiler), null),
|
||||
TextView.BufferType.SPANNABLE);
|
||||
} else {
|
||||
holder.binding.spoiler.setVisibility(View.GONE);
|
||||
|
|
Loading…
Reference in New Issue