Add callback

This commit is contained in:
Thomas 2025-05-29 17:08:59 +02:00
parent 60db57a42f
commit 9ba09c31f6
16 changed files with 56 additions and 41 deletions

View file

@ -119,7 +119,7 @@ dependencies {
implementation "org.conscrypt:conscrypt-android:2.5.2" implementation "org.conscrypt:conscrypt-android:2.5.2"
implementation 'com.vanniktech:emoji-one:0.6.0' implementation 'com.vanniktech:emoji-one:0.6.0'
implementation 'com.github.GrenderG:Toasty:1.5.2' implementation 'com.github.GrenderG:Toasty:1.5.2'
implementation "com.github.bumptech.glide:glide:4.14.2" implementation "com.github.bumptech.glide:glide:4.16.0"
implementation "com.github.bumptech.glide:okhttp3-integration:4.14.2" implementation "com.github.bumptech.glide:okhttp3-integration:4.14.2"
implementation("com.github.bumptech.glide:recyclerview-integration:4.14.2") { implementation("com.github.bumptech.glide:recyclerview-integration:4.14.2") {
// Excludes the support library because it's already included by Glide. // Excludes the support library because it's already included by Glide.

View file

@ -540,7 +540,12 @@ public class ProfileActivity extends BaseActivity {
}); });
binding.accountNote.setText( binding.accountNote.setText(
account.getSpanNote(ProfileActivity.this, account.getSpanNote(ProfileActivity.this,
binding.accountNote), binding.accountNote, () -> {
//TODO: replace this hack
binding.accountNote.setText(
account.getSpanNote(ProfileActivity.this, binding.accountNote, null), TextView.BufferType.SPANNABLE);
}),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
binding.accountNote.setMovementMethod(LinkMovementMethod.getInstance()); binding.accountNote.setMovementMethod(LinkMovementMethod.getInstance());

View file

@ -95,7 +95,7 @@ public class Account implements Serializable {
if (display_name == null || display_name.isEmpty()) { if (display_name == null || display_name.isEmpty()) {
display_name = username; display_name = username;
} }
return SpannableHelper.convert(context, display_name, null, this, null, view, true, false); return SpannableHelper.convert(context, display_name, null, this, null, view, true, false, null);
} }
public synchronized Spannable getSpanDisplayNameEmoji(Activity activity, View view) { public synchronized Spannable getSpanDisplayNameEmoji(Activity activity, View view) {
@ -106,11 +106,11 @@ public class Account implements Serializable {
} }
public synchronized Spannable getSpanDisplayNameTitle(Context context, View view, String title) { public synchronized Spannable getSpanDisplayNameTitle(Context context, View view, String title) {
return SpannableHelper.convert(context, title, null, this, null, view, true, false); return SpannableHelper.convert(context, title, null, this, null, view, true, false, null);
} }
public synchronized Spannable getSpanNote(Context context, View view) { public synchronized Spannable getSpanNote(Context context, View view, SpannableHelper.Callback callback) {
return SpannableHelper.convert(context, note, null, this, null, view, true, false); return SpannableHelper.convert(context, note, null, this, null, view, true, false, callback);
} }

View file

@ -56,7 +56,7 @@ public class Announcement {
public synchronized Spannable getSpanContent(Context context, View view) { public synchronized Spannable getSpanContent(Context context, View view) {
return SpannableHelper.convert(context, content, null, null, this, view, true, false); return SpannableHelper.convert(context, content, null, null, this, view, true, false, null);
} }
} }

View file

@ -47,7 +47,7 @@ public class Field implements Serializable {
if (verified_at != null && value != null) { if (verified_at != null && value != null) {
value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text)); value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text));
} }
Spannable spannable = SpannableHelper.convert(context, value, null, account, null, view, true, false); Spannable spannable = SpannableHelper.convert(context, value, null, account, null, view, true, false, null);
if (value_span != null && spannable != null) { if (value_span != null && spannable != null) {
spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} }
@ -57,7 +57,7 @@ public class Field implements Serializable {
public synchronized Spannable getLabelSpan(Context context, Account account, View view) { public synchronized Spannable getLabelSpan(Context context, Account account, View view) {
Spannable spannable = SpannableHelper.convert(context, name, null, account, null, view, true, false); Spannable spannable = SpannableHelper.convert(context, name, null, account, null, view, true, false, null);
if (name_span != null && spannable != null) { if (name_span != null && spannable != null) {
spannable.setSpan(name_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spannable.setSpan(name_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} }

View file

@ -61,7 +61,7 @@ public class Poll implements Serializable {
public transient Spannable span_title; public transient Spannable span_title;
public Spannable getSpanTitle(Context context, Status status,View view) { public Spannable getSpanTitle(Context context, Status status,View view) {
span_title = SpannableHelper.convert(context, title, status, null, null, view, false, false); span_title = SpannableHelper.convert(context, title, status, null, null, view, false, false, null);
return span_title; return span_title;
} }
} }

View file

@ -155,23 +155,23 @@ public class Status implements Serializable, Cloneable {
return same; return same;
} }
public synchronized Spannable getSpanContent(Context context, boolean checkRemotely, View view) { public synchronized Spannable getSpanContent(Context context, boolean checkRemotely, View view, SpannableHelper.Callback callback) {
if (contentSpan == null) { if (contentSpan == null) {
contentSpan = SpannableHelper.convert(context, content, this, null, null, checkRemotely, view, true, true); contentSpan = SpannableHelper.convert(context, content, this, null, null, checkRemotely, view, true, true, callback);
} }
return contentSpan; return contentSpan;
} }
public synchronized Spannable getSpanSpoiler(Context context, View view) { public synchronized Spannable getSpanSpoiler(Context context, View view, SpannableHelper.Callback callback) {
if (contentSpoilerSpan == null) { if (contentSpoilerSpan == null) {
contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, view, true, false); contentSpoilerSpan = SpannableHelper.convert(context, spoiler_text, this, null, null, view, true, false, callback);
} }
return contentSpoilerSpan; return contentSpoilerSpan;
} }
public synchronized Spannable getSpanTranslate(Context context, View view) { public synchronized Spannable getSpanTranslate(Context context, View view, SpannableHelper.Callback callback) {
if (contentTranslateSpan == null) { if (contentTranslateSpan == null) {
contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, view, true, true); contentTranslateSpan = SpannableHelper.convert(context, translationContent, this, null, null, view, true, true, callback);
} }
return contentTranslateSpan; return contentTranslateSpan;
} }
@ -186,8 +186,6 @@ public class Status implements Serializable, Cloneable {
BOTTOM BOTTOM
} }
public interface Callback {
void emojiFetched();
}
} }

View file

@ -62,7 +62,7 @@ public class CustomImageSpan extends ReplacementSpan {
} }
} }
public Target<Drawable> getTarget(View view, boolean animate) { public Target<Drawable> getTarget(View view, boolean animate, SpannableHelper.Callback callback) {
return new CustomTarget<>() { return new CustomTarget<>() {
@Override @Override
@ -104,10 +104,19 @@ public class CustomImageSpan extends ReplacementSpan {
} }
imageDrawable = resource; imageDrawable = resource;
view.invalidate(); view.invalidate();
if(callback != null) {
callback.emojiFetched();
}
} }
@Override @Override
public void onLoadCleared(@Nullable Drawable placeholder) { public void onLoadCleared(@Nullable Drawable placeholder) {
if(imageDrawable != null && imageDrawable instanceof Animatable) {
((Animatable) imageDrawable).stop();
imageDrawable.setCallback(null);
}
imageDrawable = null;
view.invalidate();
} }
}; };
} }

View file

@ -103,19 +103,22 @@ import io.noties.prism4j.Prism4j;
public class SpannableHelper { public class SpannableHelper {
public interface Callback {
void emojiFetched();
}
public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN"; public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN";
private static int linkColor; private static int linkColor;
private static boolean underlineLinks; private static boolean underlineLinks;
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,
View view, boolean convertHtml, boolean convertMarkdown) { View view, boolean convertHtml, boolean convertMarkdown, Callback callback) {
return convert(context, text, status, account, announcement, false, view, convertHtml, convertMarkdown); return convert(context, text, status, account, announcement, false, view, convertHtml, convertMarkdown, callback);
} }
public static Spannable convert(Context context, String text, public static Spannable convert(Context context, String text,
Status status, Account account, Announcement announcement, boolean checkRemotely, Status status, Account account, Announcement announcement, boolean checkRemotely,
View view, boolean convertHtml, boolean convertMarkdown) { View view, boolean convertHtml, boolean convertMarkdown, Callback callback) {
if (text == null) { if (text == null) {
return null; return null;
} }
@ -385,7 +388,7 @@ public class SpannableHelper {
Glide.with(context) Glide.with(context)
.asDrawable() .asDrawable()
.load(animate ? emoji.url : emoji.static_url) .load(animate ? emoji.url : emoji.static_url)
.into(customImageSpan.getTarget(view, animate)); .into(customImageSpan.getTarget(view, animate, callback));
} }
} }
} }
@ -402,7 +405,7 @@ public class SpannableHelper {
Glide.with(context) Glide.with(context)
.asDrawable() .asDrawable()
.load(url) .load(url)
.into(customImageSpan.getTarget(view, false)); .into(customImageSpan.getTarget(view, false, null));
} }
} }
@ -1014,7 +1017,7 @@ public class SpannableHelper {
Glide.with(view.getContext()) Glide.with(view.getContext())
.asDrawable() .asDrawable()
.load(animate ? emoji.url : emoji.static_url) .load(animate ? emoji.url : emoji.static_url)
.into(customImageSpan.getTarget(view, animate)); .into(customImageSpan.getTarget(view, animate, null));
} }
} }
} }

View file

@ -314,7 +314,7 @@ public class AccountAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
accountViewHolder.binding.username.setText(String.format("@%s", account.acct)); accountViewHolder.binding.username.setText(String.format("@%s", account.acct));
accountViewHolder.binding.bio.setText( accountViewHolder.binding.bio.setText(
account.getSpanNote(context, account.getSpanNote(context,
accountViewHolder.binding.bio), accountViewHolder.binding.bio, null),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} }

View file

@ -1418,7 +1418,7 @@ public class ComposeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
} }
holder.binding.statusContent.setText( holder.binding.statusContent.setText(
status.getSpanContent(context, false, status.getSpanContent(context, false,
holder.binding.statusContent), holder.binding.statusContent, ()->mRecyclerView.post(() -> notifyItemChanged(holder.getBindingAdapterPosition()))),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
holder.binding.statusContent.setMovementMethod(LongClickLinkMovementMethod.getInstance()); holder.binding.statusContent.setMovementMethod(LongClickLinkMovementMethod.getInstance());
MastodonHelper.loadPPMastodon(holder.binding.avatar, status.account); MastodonHelper.loadPPMastodon(holder.binding.avatar, status.account);
@ -1434,7 +1434,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,
holder.binding.spoiler), holder.binding.spoiler, ()->mRecyclerView.post(() -> notifyItemChanged(holder.getBindingAdapterPosition()))),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} else { } else {
holder.binding.spoiler.setVisibility(View.GONE); holder.binding.spoiler.setVisibility(View.GONE);

View file

@ -199,7 +199,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,
holder.binding.spoiler), holder.binding.spoiler, ()->mRecyclerView.post(() -> notifyItemChanged(holder.getBindingAdapterPosition()))),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} else { } else {
holder.binding.spoiler.setVisibility(View.GONE); holder.binding.spoiler.setVisibility(View.GONE);
@ -209,7 +209,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, false, conversation.last_status.getSpanContent(context, false,
holder.binding.statusContent), holder.binding.statusContent, ()->mRecyclerView.post(() -> 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));

View file

@ -604,7 +604,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.quotedMessage.cardviewContainer.setStrokeColor(ThemeHelper.getAttColor(context, R.attr.colorPrimary)); holder.binding.quotedMessage.cardviewContainer.setStrokeColor(ThemeHelper.getAttColor(context, R.attr.colorPrimary));
holder.binding.quotedMessage.statusContent.setText( holder.binding.quotedMessage.statusContent.setText(
statusToDeal.quote.getSpanContent(context, remote, statusToDeal.quote.getSpanContent(context, remote,
holder.binding.quotedMessage.statusContent), holder.binding.quotedMessage.statusContent, ()->recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()))),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
MastodonHelper.loadPPMastodon(holder.binding.quotedMessage.avatar, statusToDeal.quote.account); MastodonHelper.loadPPMastodon(holder.binding.quotedMessage.avatar, statusToDeal.quote.account);
if (statusToDeal.quote.account != null) { if (statusToDeal.quote.account != null) {
@ -619,7 +619,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.quotedMessage.spoiler.setVisibility(View.VISIBLE); holder.binding.quotedMessage.spoiler.setVisibility(View.VISIBLE);
holder.binding.quotedMessage.spoiler.setText( holder.binding.quotedMessage.spoiler.setText(
statusToDeal.quote.getSpanSpoiler(context, statusToDeal.quote.getSpanSpoiler(context,
holder.binding.quotedMessage.spoiler), holder.binding.quotedMessage.spoiler, ()->recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()))),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} else { } else {
holder.binding.quotedMessage.spoiler.setVisibility(View.GONE); holder.binding.quotedMessage.spoiler.setVisibility(View.GONE);
@ -1440,7 +1440,7 @@ 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,
holder.binding.spoiler), holder.binding.spoiler, ()->recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()))),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
statusToDeal.isExpended = true; statusToDeal.isExpended = true;
} else { } else {
@ -1453,7 +1453,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
holder.binding.spoiler.setText( holder.binding.spoiler.setText(
statusToDeal.getSpanSpoiler(context, statusToDeal.getSpanSpoiler(context,
holder.binding.spoiler), holder.binding.spoiler, ()->recyclerView.post(() -> adapter.notifyItemChanged(holder.getBindingAdapterPosition()))),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
} }
if (statusToDeal.isExpended) { if (statusToDeal.isExpended) {
@ -1506,7 +1506,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
//--- MAIN CONTENT --- //--- MAIN CONTENT ---
holder.binding.statusContent.setText( holder.binding.statusContent.setText(
statusToDeal.getSpanContent(context, remote, statusToDeal.getSpanContent(context, remote,
holder.binding.statusContent), 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);
@ -1531,7 +1531,7 @@ 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,
holder.binding.statusContentTranslated), 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);

View file

@ -241,7 +241,7 @@ public class StatusDirectMessageAdapter extends RecyclerView.Adapter<RecyclerVie
status.underlined = true; status.underlined = true;
holder.binding.messageContent.setText( holder.binding.messageContent.setText(
status.getSpanContent(context, false, status.getSpanContent(context, false,
holder.binding.messageContent), holder.binding.messageContent, ()->mRecyclerView.post(() -> notifyItemChanged(holder.getBindingAdapterPosition()))),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
holder.binding.messageContent.setMovementMethod(LongClickLinkMovementMethod.getInstance()); holder.binding.messageContent.setMovementMethod(LongClickLinkMovementMethod.getInstance());
if (measuredWidth <= 0 && status.media_attachments != null && status.media_attachments.size() > 0) { if (measuredWidth <= 0 && status.media_attachments != null && status.media_attachments.size() > 0) {

View file

@ -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, false, status.getSpanContent(context, false,
holder.binding.statusContent), holder.binding.statusContent, ()-> notifyItemChanged(holder.getBindingAdapterPosition())),
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,
holder.binding.spoiler), 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);

View file

@ -106,7 +106,7 @@ public class SuggestionAdapter extends RecyclerView.Adapter<RecyclerView.ViewHol
holder.binding.username.setText(String.format("@%s", account.acct)); holder.binding.username.setText(String.format("@%s", account.acct));
holder.binding.bio.setText( holder.binding.bio.setText(
account.getSpanNote(context, account.getSpanNote(context,
holder.binding.bio), holder.binding.bio, null),
TextView.BufferType.SPANNABLE); TextView.BufferType.SPANNABLE);
holder.binding.followAction.setOnClickListener(v -> { holder.binding.followAction.setOnClickListener(v -> {