diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Account.java b/app/src/main/java/app/fedilab/android/client/entities/api/Account.java index 94623d4b0..8e2457464 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Account.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Account.java @@ -83,12 +83,12 @@ public class Account implements Serializable { if (display_name == null) { display_name = username; } - return SpannableHelper.convert(context, display_name, null, this, true, viewWeakReference); + return SpannableHelper.convert(context, display_name, null, this, null, true, viewWeakReference); } public synchronized Spannable getSpanNote(Context context, WeakReference viewWeakReference) { - return SpannableHelper.convert(context, note, null, this, true, viewWeakReference); + return SpannableHelper.convert(context, note, null, this, null, true, viewWeakReference); } public transient RelationShip relationShip; diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Announcement.java b/app/src/main/java/app/fedilab/android/client/entities/api/Announcement.java index a642040c6..b015313fd 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Announcement.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Announcement.java @@ -14,13 +14,18 @@ package app.fedilab.android.client.entities.api; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ +import android.content.Context; import android.text.Spannable; +import android.view.View; import com.google.gson.annotations.SerializedName; +import java.lang.ref.WeakReference; import java.util.Date; import java.util.List; +import app.fedilab.android.helper.SpannableHelper; + public class Announcement { @SerializedName("id") public String id; @@ -49,7 +54,9 @@ public class Announcement { @SerializedName("reactions") public List reactions; - //Some extra spannable element - They will be filled automatically when fetching the status - public transient Spannable span_content; - public transient boolean emojiFetched = false; + + public synchronized Spannable getSpanContent(Context context, WeakReference viewWeakReference) { + return SpannableHelper.convert(context, content, null, null, this, true, viewWeakReference); + } + } diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Field.java b/app/src/main/java/app/fedilab/android/client/entities/api/Field.java index 55754dc6c..da8571564 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Field.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Field.java @@ -14,13 +14,22 @@ package app.fedilab.android.client.entities.api; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ +import android.content.Context; import android.text.Spannable; +import android.text.style.ForegroundColorSpan; +import android.view.View; + +import androidx.core.content.ContextCompat; import com.google.gson.annotations.SerializedName; import java.io.Serializable; +import java.lang.ref.WeakReference; import java.util.Date; +import app.fedilab.android.R; +import app.fedilab.android.helper.SpannableHelper; + public class Field implements Serializable { @SerializedName("name") public String name; @@ -30,7 +39,19 @@ public class Field implements Serializable { public Date verified_at; //Some extra spannable element - They will be filled automatically when fetching the account - public transient Spannable value_span; + private ForegroundColorSpan value_span; + + public synchronized Spannable getValueSpan(Context context, Account account, WeakReference viewWeakReference) { + + if (verified_at != null && value != null) { + value_span = new ForegroundColorSpan(ContextCompat.getColor(context, R.color.verified_text)); + } + Spannable spannable = SpannableHelper.convert(context, value, null, account, null, true, viewWeakReference); + if (value_span != null && spannable != null) { + spannable.setSpan(value_span, 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return spannable; + } public static class FieldParams implements Serializable { @SerializedName("name") diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Poll.java b/app/src/main/java/app/fedilab/android/client/entities/api/Poll.java index 4cd8cff9f..a4e8b5c68 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Poll.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Poll.java @@ -61,7 +61,7 @@ public class Poll implements Serializable { public Spannable span_title; public Spannable getSpanTitle(Context context, Status status, WeakReference viewWeakReference) { - span_title = SpannableHelper.convert(context, title, status, null, true, viewWeakReference); + span_title = SpannableHelper.convert(context, title, status, null, null, true, viewWeakReference); return span_title; } } diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java index a1e3e70d9..b75125e18 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java @@ -109,7 +109,7 @@ public class Status implements Serializable, Cloneable { //Some extra spannable element - They will be filled automatically when fetching the status public synchronized Spannable getSpanContent(Context context, WeakReference viewWeakReference) { - return SpannableHelper.convert(context, content, this, null, true, viewWeakReference); + return SpannableHelper.convert(context, content, this, null, null, true, viewWeakReference); } @@ -118,11 +118,11 @@ public class Status implements Serializable, Cloneable { } public synchronized Spannable getSpanSpoiler(Context context, WeakReference viewWeakReference) { - return SpannableHelper.convert(context, spoiler_text, this, null, true, viewWeakReference); + return SpannableHelper.convert(context, spoiler_text, this, null, null, true, viewWeakReference); } public synchronized Spannable getSpanTranslate(Context context, WeakReference viewWeakReference) { - return SpannableHelper.convert(context, translationContent, this, null, true, viewWeakReference); + return SpannableHelper.convert(context, translationContent, this, null, null, true, viewWeakReference); } @NonNull diff --git a/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java b/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java index 6f3745250..8b860a691 100644 --- a/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java +++ b/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java @@ -1,125 +1,111 @@ package app.fedilab.android.helper; -/* Copyright 2022 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ import android.content.Context; import android.content.SharedPreferences; +import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; -import android.text.Spannable; -import android.text.style.ImageSpan; +import android.text.style.ReplacementSpan; +import android.util.Log; import android.view.View; 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.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.lang.ref.WeakReference; import app.fedilab.android.R; -import app.fedilab.android.client.entities.api.Emoji; - -public class CustomEmoji { - public static void displayEmoji(Context context, List emojis, Spannable content, View view, String id, Callback listener) { - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(view.getContext()); - boolean animate = !sharedpreferences.getBoolean(view.getContext().getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false); - int count = 1; - for (Emoji emoji : emojis) { - int finalCount = count; - Glide.with(view.getContext()) - .asDrawable() - .load(animate ? emoji.url : emoji.static_url) - .into( - new CustomTarget() { - @Override - public void onLoadFailed(@Nullable Drawable errorDrawable) { - super.onLoadFailed(errorDrawable); - if (finalCount == emojis.size() && listener != null) { - listener.allEmojisfound(id); - } - } +public class CustomEmoji extends ReplacementSpan { + private final View view; + private final float scale; + private final WeakReference viewWeakReference; + private Drawable imageDrawable; - @Override - public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { - if (content == null) { - return; - } - Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL) - .matcher(content); - while (matcher.find()) { - ImageSpan imageSpan; - resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); - resource.setVisible(true, true); - imageSpan = new ImageSpan(resource); - content.setSpan( - imageSpan, matcher.start(), - matcher.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - } - if (animate && resource instanceof Animatable) { - Drawable.Callback callback = resource.getCallback(); - resource.setCallback(new Drawable.Callback() { - @Override - public void invalidateDrawable(@NonNull Drawable drawable) { - if (callback != null) { - callback.invalidateDrawable(drawable); - } - view.invalidate(); - } - @Override - public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) { - if (callback != null) { - callback.scheduleDrawable(drawable, runnable, l); - } - } + CustomEmoji(WeakReference viewWeakReference) { + Context mContext = viewWeakReference.get().getContext(); + this.viewWeakReference = viewWeakReference; + view = viewWeakReference.get(); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(mContext); + scale = sharedpreferences.getFloat(mContext.getString(R.string.SET_FONT_SCALE), 1.0f); + } - @Override - public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) { - if (callback != null) { - callback.unscheduleDrawable(drawable, runnable); - } - } - }); - ((Animatable) resource).start(); + @Override + public int getSize(@NonNull Paint paint, CharSequence charSequence, int i, int i1, @Nullable Paint.FontMetricsInt fontMetricsInt) { + if (fontMetricsInt != null) { + Paint.FontMetrics fontMetrics = paint.getFontMetrics(); + fontMetricsInt.top = (int) fontMetrics.top; + fontMetricsInt.ascent = (int) fontMetrics.ascent; + fontMetricsInt.descent = (int) fontMetrics.descent; + fontMetricsInt.bottom = (int) fontMetrics.bottom; + } + return (int) (paint.getTextSize() * scale); + } - } - if (finalCount == emojis.size() && listener != null) { - listener.allEmojisfound(id); - } - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - } - ); - count++; + @Override + public void draw(@NonNull Canvas canvas, CharSequence charSequence, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) { + if (imageDrawable != null) { + canvas.save(); + int emojiSize = (int) (paint.getTextSize() * scale); + Drawable drawable = imageDrawable; + drawable.setBounds(0, 0, emojiSize, emojiSize); + int transY = bottom - drawable.getBounds().bottom; + transY -= paint.getFontMetrics().descent / 2; + canvas.translate(x, (float) transY); + drawable.draw(canvas); + canvas.restore(); } } - public interface Callback { - void allEmojisfound(String id); - } + public Target getTarget(boolean animate) { + return new CustomTarget() { + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + Log.v(Helper.TAG, "resource: " + resource); + Log.v(Helper.TAG, "instanceof: " + (resource instanceof Animatable)); + View view = viewWeakReference.get(); + if (animate && resource instanceof Animatable) { + Drawable.Callback callback = resource.getCallback(); + resource.setCallback(new Drawable.Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable drawable) { + if (callback != null) { + callback.invalidateDrawable(drawable); + } + view.invalidate(); + } -} + @Override + public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) { + if (callback != null) { + callback.scheduleDrawable(drawable, runnable, l); + } + } + + @Override + public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) { + if (callback != null) { + callback.unscheduleDrawable(drawable, runnable); + } + } + }); + ((Animatable) resource).start(); + } + imageDrawable = resource; + view.invalidate(); + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + } + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java index 8c9a5bbe3..56ab2f4e6 100644 --- a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java @@ -25,8 +25,6 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.graphics.drawable.Animatable; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -38,7 +36,6 @@ import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.TextPaint; import android.text.style.ClickableSpan; -import android.text.style.ImageSpan; import android.text.style.URLSpan; import android.util.Patterns; import android.view.LayoutInflater; @@ -46,13 +43,10 @@ import android.view.View; import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.preference.PreferenceManager; import com.bumptech.glide.Glide; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.transition.Transition; import java.io.IOException; import java.lang.ref.WeakReference; @@ -73,6 +67,7 @@ import app.fedilab.android.activities.ContextActivity; import app.fedilab.android.activities.HashTagActivity; import app.fedilab.android.activities.ProfileActivity; import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.client.entities.api.Announcement; import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.client.entities.api.Emoji; import app.fedilab.android.client.entities.api.Mention; @@ -85,7 +80,7 @@ public class SpannableHelper { public static final String CLICKABLE_SPAN = "CLICKABLE_SPAN"; public static Spannable convert(Context context, String text, - Status status, Account account, + Status status, Account account, Announcement announcement, boolean convertHtml, WeakReference viewWeakReference) { @@ -138,62 +133,16 @@ public class SpannableHelper { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); boolean animate = !sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_ANIMATED_EMOJI), false); for (Emoji emoji : emojiList) { - Glide.with(context) - .asDrawable() - .load(animate ? emoji.url : emoji.static_url) - .into( - new CustomTarget() { - - @Override - public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { - Matcher matcher = Pattern.compile(":" + emoji.shortcode + ":", Pattern.LITERAL) - .matcher(content); - while (matcher.find()) { - ImageSpan imageSpan; - resource.setBounds(0, 0, (int) Helper.convertDpToPixel(20, context), (int) Helper.convertDpToPixel(20, context)); - resource.setVisible(true, true); - imageSpan = new ImageSpan(resource); - content.setSpan( - imageSpan, matcher.start(), - matcher.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - } - if (animate && resource instanceof Animatable) { - Drawable.Callback callback = resource.getCallback(); - resource.setCallback(new Drawable.Callback() { - @Override - public void invalidateDrawable(@NonNull Drawable drawable) { - if (callback != null) { - callback.invalidateDrawable(drawable); - } - view.invalidate(); - } - - @Override - public void scheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable, long l) { - if (callback != null) { - callback.scheduleDrawable(drawable, runnable, l); - } - } - - @Override - public void unscheduleDrawable(@NonNull Drawable drawable, @NonNull Runnable runnable) { - if (callback != null) { - callback.unscheduleDrawable(drawable, runnable); - } - } - }); - ((Animatable) resource).start(); - - } - - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - } - ); + 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)); + } } } return trimSpannable(new SpannableStringBuilder(content)); diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AnnouncementAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/AnnouncementAdapter.java index 8ae35dafd..6a6d9fad9 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/AnnouncementAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/AnnouncementAdapter.java @@ -37,6 +37,7 @@ import com.vanniktech.emoji.EmojiManager; import com.vanniktech.emoji.EmojiPopup; import com.vanniktech.emoji.one.EmojiOneProvider; +import java.lang.ref.WeakReference; import java.util.List; import app.fedilab.android.BaseMainActivity; @@ -44,7 +45,6 @@ import app.fedilab.android.R; import app.fedilab.android.client.entities.api.Announcement; import app.fedilab.android.client.entities.api.Reaction; import app.fedilab.android.databinding.DrawerAnnouncementBinding; -import app.fedilab.android.helper.CustomEmoji; import app.fedilab.android.helper.Helper; import app.fedilab.android.viewmodel.mastodon.AnnouncementsVM; @@ -88,13 +88,10 @@ public class AnnouncementAdapter extends RecyclerView.Adapter { - if (!announcement.emojiFetched) { - announcement.emojiFetched = true; - holder.binding.content.post(() -> notifyItemChanged(position)); - } - }); - holder.binding.content.setText(announcement.span_content, TextView.BufferType.SPANNABLE); + holder.binding.content.setText( + announcement.getSpanContent(context, + new WeakReference<>(holder.binding.content)), + TextView.BufferType.SPANNABLE); if (announcement.starts_at != null) { String dateIni; String dateEnd; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/FieldAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/FieldAdapter.java index df8bbe270..b4b39fc10 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/FieldAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/FieldAdapter.java @@ -16,9 +16,7 @@ package app.fedilab.android.ui.drawer; import android.content.Context; -import android.text.Spannable; import android.text.method.LinkMovementMethod; -import android.text.style.ForegroundColorSpan; import android.view.LayoutInflater; import android.view.ViewGroup; import android.widget.TextView; @@ -27,9 +25,11 @@ import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; +import java.lang.ref.WeakReference; import java.util.List; import app.fedilab.android.R; +import app.fedilab.android.client.entities.api.Account; import app.fedilab.android.client.entities.api.Field; import app.fedilab.android.databinding.DrawerFieldBinding; @@ -38,6 +38,7 @@ public class FieldAdapter extends RecyclerView.Adapter fields; private Context context; + private Account account; public FieldAdapter(List fields) { this.fields = fields; @@ -66,9 +67,11 @@ public class FieldAdapter extends RecyclerView.Adapter(holder.binding.value)), + TextView.BufferType.SPANNABLE); holder.binding.value.setMovementMethod(LinkMovementMethod.getInstance()); holder.binding.label.setText(field.name); }