~captainepoch/husky-devel

husky: NotificationsAdapter: implement custom emoji reacts v1 PROPOSED

: 1
 NotificationsAdapter: implement custom emoji reacts

 5 files changed, 68 insertions(+), 18 deletions(-)
#780004 stable.yml failed
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~captainepoch/husky-devel/patches/32969/mbox | git am -3
Learn more about email & git

[PATCH husky] NotificationsAdapter: implement custom emoji reacts Export this patch

From: Hélène <pleroma-dev@helene.moe>

If the emoji_url field is specified in the notification data, it will be
used to represent the emoji in the notification text. As such, the
Notification entity data class has been updated accordingly, to match
the current implementation at AkkomaDev/Akkoma, which may later be
integrated by more Pleroma-based backends.

The SpannableBuilder formatting is a bit hacky, but it works. Ideally,
something like Phrase should be used for localization and Spannable
formatting. However, this would be a different undertaking of its own,
and would require more work on refactoring multiple parts of the
application code.

Requires: https://lists.sr.ht/~captainepoch/husky-devel/patches/32861
Signed-off-by: Hélène <pleroma-dev@helene.moe>
---
Please note that this patch requires 

 .../tusky/adapter/NotificationsAdapter.java   | 45 +++++++++++++++----
 .../tusky/entity/Notification.kt              |  1 +
 .../tusky/fragment/NotificationsFragment.java | 27 +++++++----
 .../tusky/util/ViewDataUtils.java             |  1 +
 .../tusky/viewdata/NotificationViewData.java  | 12 ++++-
 5 files changed, 68 insertions(+), 18 deletions(-)

diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
index af0ac40..6ae9203 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/adapter/NotificationsAdapter.java
@@ -40,6 +40,8 @@ import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.github.penfeizhou.animation.glide.AnimationDecoderOption;
import com.keylesspalace.tusky.R;
import com.keylesspalace.tusky.entity.Account;
import com.keylesspalace.tusky.entity.Emoji;
@@ -51,6 +53,8 @@ import com.keylesspalace.tusky.util.CardViewMode;
import com.keylesspalace.tusky.util.CustomEmojiHelper;
import com.keylesspalace.tusky.util.ImageLoadingHelper;
import com.keylesspalace.tusky.util.LinkHelper;
import com.keylesspalace.tusky.util.MIME;
import com.keylesspalace.tusky.util.SmallEmojiSpan;
import com.keylesspalace.tusky.util.SmartLengthInputFilter;
import com.keylesspalace.tusky.util.StatusDisplayOptions;
import com.keylesspalace.tusky.util.StringUtils;
@@ -59,6 +63,7 @@ import com.keylesspalace.tusky.util.TimestampUtils;
import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData;

import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@@ -541,8 +546,8 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
            Notification.Type type = notificationViewData.getType();

            Context context = message.getContext();
            String wholeMessage;
            Drawable icon;
            SpannableStringBuilder builder = new SpannableStringBuilder();
            switch (type) {
                default:
                case FAVOURITE: {
@@ -553,7 +558,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
                    }

                    String format = context.getString(R.string.notification_favourite_format);
                    wholeMessage = String.format(format, displayName);
                    builder.append(String.format(format, displayName));
                    break;
                }
                case REBLOG: {
@@ -564,7 +569,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
                    }

                    String format = context.getString(R.string.notification_reblog_format);
                    wholeMessage = String.format(format, displayName);
                    builder.append(String.format(format, displayName));
                    break;
                }
                case STATUS: {
@@ -575,7 +580,7 @@ public class NotificationsAdapter extends RecyclerView.Adapter {
                    }

                    String format = context.getString(R.string.notification_subscription_format);
                    wholeMessage = String.format(format, displayName);
                    builder.append(String.format(format, displayName));
                    break;
                }
                case EMOJI_REACTION: {
@@ -587,15 +592,39 @@ public class NotificationsAdapter extends RecyclerView.Adapter {

                    String format = context.getString(R.string.notification_emoji_format);
                    String emojiCode = notificationViewData.getEmoji();
                    wholeMessage = String.format(format, displayName, emojiCode);
                    builder.append(String.format(format, displayName, emojiCode));

                    final String emojiUrl = notificationViewData.getEmojiUrl();
                    if(emojiUrl != null) {
                        // terrible hack... ideally, there should be a CharSequence formatter
                        final int emojiPosition = format.indexOf("%s", 1)
                                - "%s".length() + displayName.length();
                        final var animateEmojis = statusDisplayOptions.animateEmojiReacts();

                        var span = new SmallEmojiSpan(new WeakReference<View>(message));
                        builder.setSpan(span, emojiPosition, emojiPosition + emojiCode.length(),
                                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

                        var glideRequest = Glide.with(message).load(emojiUrl)
                                .set(AnimationDecoderOption.DISABLE_ANIMATION_GIF_DECODER, !animateEmojis)
                                .set(AnimationDecoderOption.DISABLE_ANIMATION_WEBP_DECODER, !animateEmojis)
                                .set(AnimationDecoderOption.DISABLE_ANIMATION_APNG_DECODER, !animateEmojis);
                        var mimetype = CustomEmojiHelper.getMimeType(emojiUrl);
                        if(mimetype == MIME.SVG) {
                            glideRequest = glideRequest
                                    .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
                                    .override(512, 512);
                        }
                        glideRequest.into(span.getTarget(animateEmojis));
                    }

                    break;
                }
            }
            message.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
            final SpannableString str = new SpannableString(wholeMessage);
            str.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
            builder.setSpan(new StyleSpan(Typeface.BOLD), 0, displayName.length(),
                    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            CharSequence emojifiedText = CustomEmojiHelper.emojify(str, notificationViewData.getAccount().getEmojis(), message, true);
            CharSequence emojifiedText = CustomEmojiHelper.emojify(builder, notificationViewData.getAccount().getEmojis(), message, true);
            message.setText(emojifiedText);

            if (statusViewData != null) {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt b/husky/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
index c57addc..2b3556e 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/entity/Notification.kt
@@ -31,6 +31,7 @@ data class Notification(
        val status: Status?,
        val pleroma: PleromaNotification? = null,
        val emoji: String? = null,
        @SerializedName("emoji_url") val emojiUrl: String? = null,
        @SerializedName("chat_message") val chatMessage: ChatMessage? = null,
        @SerializedName("created_at") val createdAt: Date? = null,
        val target: Account? = null) {
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
index 2233340..7c9c20b 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java
@@ -506,7 +506,8 @@ public class NotificationsFragment extends SFragment implements

        NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
                viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(), viewdata.getTarget());
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
                viewdata.getEmojiUrl(), viewdata.getTarget());
        notifications.setPairedItem(position, newViewData);
        updateAdapter();
    }
@@ -540,7 +541,8 @@ public class NotificationsFragment extends SFragment implements

        NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
                viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(), viewdata.getTarget());
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
                viewdata.getEmojiUrl(), viewdata.getTarget());

        notifications.setPairedItem(position, newViewData);
        updateAdapter();
@@ -575,7 +577,8 @@ public class NotificationsFragment extends SFragment implements

        NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
                viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(), viewdata.getTarget());
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
                viewdata.getEmojiUrl(), viewdata.getTarget());

        notifications.setPairedItem(position, newViewData);
        updateAdapter();
@@ -604,7 +607,8 @@ public class NotificationsFragment extends SFragment implements

        NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
                viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(), viewdata.getTarget());
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
                viewdata.getEmojiUrl(), viewdata.getTarget());

        notifications.setPairedItem(position, newViewData);
        updateAdapter();
@@ -651,7 +655,8 @@ public class NotificationsFragment extends SFragment implements
                        .setIsExpanded(expanded)
                        .createStatusViewData();
        NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
                old.getId(), old.getAccount(), statusViewData, old.getEmoji(), old.getTarget());
                old.getId(), old.getAccount(), statusViewData, old.getEmoji(),
                old.getEmojiUrl(), old.getTarget());
        notifications.setPairedItem(position, notificationViewData);
        updateAdapter();
    }
@@ -665,7 +670,8 @@ public class NotificationsFragment extends SFragment implements
                        .setIsShowingSensitiveContent(isShowing)
                        .createStatusViewData();
        NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
                old.getId(), old.getAccount(), statusViewData, old.getEmoji(), old.getTarget());
                old.getId(), old.getAccount(), statusViewData, old.getEmoji(),
                old.getEmojiUrl(), old.getTarget());
        notifications.setPairedItem(position, notificationViewData);
        updateAdapter();
    }
@@ -679,7 +685,8 @@ public class NotificationsFragment extends SFragment implements
                        .setMuted(isMuted)
                        .createStatusViewData();
        NotificationViewData notificationViewData = new NotificationViewData.Concrete(old.getType(),
                old.getId(), old.getAccount(), statusViewData, old.getEmoji(), old.getTarget());
                old.getId(), old.getAccount(), statusViewData, old.getEmoji(),
                old.getEmojiUrl(), old.getTarget());
        notifications.setPairedItem(position, notificationViewData);
        updateAdapter();
    }
@@ -695,7 +702,8 @@ public class NotificationsFragment extends SFragment implements

        NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
                viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(), viewdata.getTarget());
                viewDataBuilder.createStatusViewData(), viewdata.getEmoji(),
                viewdata.getEmojiUrl(), viewdata.getTarget());

        notifications.setPairedItem(position, newViewData);
    }
@@ -752,6 +760,7 @@ public class NotificationsFragment extends SFragment implements
                concreteNotification.getAccount(),
                updatedStatus,
                concreteNotification.getEmoji(),
                concreteNotification.getEmojiUrl(),
                concreteNotification.getTarget()
        );
        notifications.setPairedItem(position, updatedNotification);
@@ -1446,7 +1455,7 @@ public class NotificationsFragment extends SFragment implements
        NotificationViewData.Concrete newViewData = new NotificationViewData.Concrete(
                viewdata.getType(), viewdata.getId(), viewdata.getAccount(),
                ViewDataUtils.statusToViewData(newStatus, false, false),
                viewdata.getEmoji(), viewdata.getTarget());
                viewdata.getEmoji(), viewdata.getEmojiUrl(), viewdata.getTarget());

        notifications.setPairedItem(position, newViewData);
        updateAdapter();
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java b/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java
index abcd8d8..229befb 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/ViewDataUtils.java
@@ -94,6 +94,7 @@ public final class ViewDataUtils {
                        alwaysOpenSpoiler
                ),
                notification.getEmoji(),
                notification.getEmojiUrl(),
                notification.getTarget()
        );
    }
diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java b/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java
index 845ecc2..52a9bf1 100644
--- a/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java
+++ b/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/NotificationViewData.java
@@ -50,16 +50,20 @@ public abstract class NotificationViewData {
        @Nullable
        private final String emoji;
        @Nullable
        private final String emojiUrl;
        @Nullable
        private final Account target; // move notification

        public Concrete(Notification.Type type, String id, Account account,
                        @Nullable StatusViewData.Concrete statusViewData,
                        @Nullable String emoji, @Nullable Account target) {
                        @Nullable String emoji, @Nullable String emojiUrl,
                        @Nullable Account target) {
            this.type = type;
            this.id = id;
            this.account = account;
            this.statusViewData = statusViewData;
            this.emoji = emoji;
            this.emojiUrl = emojiUrl;
            this.target = target;
        }

@@ -85,6 +89,11 @@ public abstract class NotificationViewData {
			return emoji;
        }

        @Nullable
        public String getEmojiUrl() {
            return emojiUrl;
        }

        @Nullable
        public Account getTarget() {
            return target;
@@ -104,6 +113,7 @@ public abstract class NotificationViewData {
                    Objects.equals(id, concrete.id) &&
                    account.getId().equals(concrete.account.getId()) &&
                    (emoji != null && concrete.emoji != null && emoji.equals(concrete.emoji)) &&
                    (emojiUrl != null && concrete.emojiUrl != null && emojiUrl.equals(concrete.emojiUrl)) &&
                    (target != null && concrete.target != null && target.getId().equals(concrete.target.getId())) &&
                    (statusViewData == concrete.statusViewData ||
                            statusViewData != null &&
-- 
2.36.1
husky/patches/stable.yml: FAILED in 6m47s

[NotificationsAdapter: implement custom emoji reacts][0] from [][1]

[0]: https://lists.sr.ht/~captainepoch/husky-devel/patches/32969
[1]: mailto:pleroma-dev@helene.moe

✗ #780004 FAILED husky/patches/stable.yml https://builds.sr.ht/~captainepoch/job/780004