From b85335e8d428bd88bf182fd7f26e784f69a3129e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne?= Date: Sat, 25 Jun 2022 14:07:07 +0200 Subject: [PATCH] NotificationsAdapter: implement custom emoji reacts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. Signed-off-by: Hélène Signed-off-by: Adolfo Santiago --- .../tusky/adapter/NotificationsAdapter.java | 27 +++++++++++++------ .../tusky/entity/Notification.kt | 1 + .../tusky/fragment/NotificationsFragment.java | 27 ++++++++++++------- .../tusky/util/ViewDataUtils.java | 1 + .../tusky/viewdata/NotificationViewData.java | 12 ++++++++- 5 files changed, 50 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 51fb5e1..05ea7bd 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 @@ -540,8 +540,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: { @@ -552,7 +552,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: { @@ -563,7 +563,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: { @@ -574,7 +574,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: { @@ -586,15 +586,26 @@ 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(); + + var span = CustomEmojiHelper.createEmojiSpan(emojiUrl, message, true); + builder.setSpan(span, emojiPosition, emojiPosition + emojiCode.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + 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 fb765cb..e9354d2 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 @@ -505,7 +505,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(); } @@ -539,7 +540,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(); @@ -574,7 +576,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(); @@ -603,7 +606,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(); @@ -650,7 +654,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(); } @@ -664,7 +669,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(); } @@ -678,7 +684,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(); } @@ -694,7 +701,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); } @@ -751,6 +759,7 @@ public class NotificationsFragment extends SFragment implements concreteNotification.getAccount(), updatedStatus, concreteNotification.getEmoji(), + concreteNotification.getEmojiUrl(), concreteNotification.getTarget() ); notifications.setPairedItem(position, updatedNotification); @@ -1445,7 +1454,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 &&