~captainepoch/husky-devel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
2 2

[PATCH husky] NotificationsAdapter: implement custom emoji reacts

Details
Message ID
<20220614022936.4829-1-pleroma-dev@helene.moe>
DKIM signature
pass
Download raw message
Patch: +68 -18
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] build failed

builds.sr.ht <builds@sr.ht>
Details
Message ID
<CKPICDLWRBZE.3L97DYKMCM55L@cirno>
In-Reply-To
<20220614022936.4829-1-pleroma-dev@helene.moe> (view parent)
DKIM signature
missing
Download raw message
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]: pleroma-dev@helene.moe

✗ #780004 FAILED husky/patches/stable.yml https://builds.sr.ht/~captainepoch/job/780004
Details
Message ID
<20220705135108.7i3bpu4mqzr7r5au@MacBook-Pro-de-Adolfo.local>
In-Reply-To
<20220614022936.4829-1-pleroma-dev@helene.moe> (view parent)
DKIM signature
pass
Download raw message
Superseded by https://lists.sr.ht/~captainepoch/husky-devel/patches/33282
Reply to thread Export thread (mbox)