From cb96ecd30ddcfe6f5af8d125616e17ae4ef7fec4 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Fri, 21 Nov 2025 07:35:55 +0100 Subject: [PATCH] Add chip/avatars to Notification Signed-off-by: tobiasKaminsky --- .../ui/activity/NotificationsActivity.kt | 2 +- .../ui/adapter/NotificationListAdapter.java | 85 ++++++++++++++++--- .../res/layout/notification_list_item.xml | 3 +- 3 files changed, 74 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt index dc24fb140963..c00bab07244f 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/NotificationsActivity.kt @@ -295,7 +295,7 @@ class NotificationsActivity : private fun initializeAdapter() { initializeClient() if (adapter == null) { - adapter = NotificationListAdapter(client, this, viewThemeUtils) + adapter = NotificationListAdapter(client, this, viewThemeUtils, accountManager) binding.list.adapter = adapter } } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java index 67ca8c616846..739d38f1da1e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.java @@ -11,9 +11,11 @@ package com.owncloud.android.ui.adapter; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Typeface; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.text.Spannable; import android.text.SpannableStringBuilder; @@ -27,9 +29,12 @@ import android.widget.LinearLayout; import com.google.android.material.button.MaterialButton; +import com.google.android.material.chip.ChipDrawable; import com.nextcloud.android.common.ui.theme.utils.ColorRole; +import com.nextcloud.client.account.CurrentAccountProvider; import com.nextcloud.common.NextcloudClient; import com.nextcloud.utils.GlideHelper; +import com.nextcloud.utils.text.Spans; import com.owncloud.android.R; import com.owncloud.android.databinding.NotificationListItemBinding; import com.owncloud.android.lib.common.utils.Log_OC; @@ -50,11 +55,13 @@ import androidx.appcompat.widget.PopupMenu; import androidx.core.content.res.ResourcesCompat; import androidx.recyclerview.widget.RecyclerView; +import thirdparties.fresco.BetterImageSpan; /** * This Adapter populates a RecyclerView with all notifications for an account within the app. */ -public class NotificationListAdapter extends RecyclerView.Adapter { +public class NotificationListAdapter extends RecyclerView.Adapter + implements DisplayUtils.AvatarGenerationListener { private static final String FILE = "file"; private static final String ACTION_TYPE_WEB = "WEB"; private final StyleSpan styleSpanBold = new StyleSpan(Typeface.BOLD); @@ -64,14 +71,17 @@ public class NotificationListAdapter extends RecyclerView.Adapter(); this.client = client; this.notificationsActivity = notificationsActivity; this.viewThemeUtils = viewThemeUtils; + this.currentAccountProvider = currentAccountProvider; foregroundColorSpanBlack = new ForegroundColorSpan( notificationsActivity.getResources().getColor(R.color.text_color)); } @@ -107,12 +117,6 @@ public void onBindViewHolder(@NonNull NotificationViewHolder holder, int positio notification.getLink())); holder.binding.subject.setText(subject); } else { - if (!TextUtils.isEmpty(notification.subjectRich)) { - holder.binding.subject.setText(makeSpecialPartsBold(notification)); - } else { - holder.binding.subject.setText(subject); - } - if (file != null && !TextUtils.isEmpty(file.id)) { holder.binding.subject.setOnClickListener(v -> { Intent intent = new Intent(notificationsActivity, FileDisplayActivity.class); @@ -124,6 +128,12 @@ public void onBindViewHolder(@NonNull NotificationViewHolder holder, int positio } } + if (TextUtils.isEmpty(notification.subjectRich)) { + holder.binding.subject.setText(subject); + } else { + holder.binding.subject.setText(makeSpecialPartsBold(notification)); + } + if (notification.getMessage() != null && !notification.getMessage().isEmpty()) { holder.binding.message.setText(notification.getMessage()); holder.binding.message.setVisibility(View.VISIBLE); @@ -308,6 +318,7 @@ public void setButtons(NotificationViewHolder holder, Notification notification) private SpannableStringBuilder makeSpecialPartsBold(Notification notification) { String text = notification.getSubjectRich(); SpannableStringBuilder ssb = new SpannableStringBuilder(text); + Context context = notificationsActivity; int openingBrace = text.indexOf('{'); int closingBrace; @@ -318,13 +329,38 @@ private SpannableStringBuilder makeSpecialPartsBold(Notification notification) { RichObject richObject = notification.subjectRichParameters.get(replaceablePart); if (richObject != null) { - String name = richObject.getName(); - ssb.replace(openingBrace, closingBrace, name); - text = ssb.toString(); - closingBrace = openingBrace + name.length(); + if ("user".equals(richObject.getType())) { + String name = richObject.getName(); + + ChipDrawable drawableForChip = getDrawableForMentionChipSpan(R.xml.chip_others, name); + + Spans.MentionChipSpan mentionChipSpan = new Spans.MentionChipSpan(drawableForChip, + BetterImageSpan.ALIGN_CENTER, + richObject.id, + name + ); + + DisplayUtils.setAvatar( + currentAccountProvider.getUser(), + richObject.id, + name, + this, + context.getResources().getDimension(R.dimen.standard_padding), + context.getResources(), + drawableForChip, + context + ); + + ssb.setSpan(mentionChipSpan, openingBrace, closingBrace, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } else { + String name = richObject.getName(); + ssb.replace(openingBrace, closingBrace, name); + text = ssb.toString(); + closingBrace = openingBrace + name.length(); - ssb.setSpan(styleSpanBold, openingBrace, closingBrace, 0); - ssb.setSpan(foregroundColorSpanBlack, openingBrace, closingBrace, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.setSpan(styleSpanBold, openingBrace, closingBrace, 0); + ssb.setSpan(foregroundColorSpanBlack, openingBrace, closingBrace, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } openingBrace = text.indexOf('{', closingBrace); } @@ -332,6 +368,17 @@ private SpannableStringBuilder makeSpecialPartsBold(Notification notification) { return ssb; } + private ChipDrawable getDrawableForMentionChipSpan(int chipResource, String text) { + ChipDrawable chip = ChipDrawable.createFromResource(notificationsActivity, chipResource); + chip.setEllipsize(TextUtils.TruncateAt.MIDDLE); + chip.setLayoutDirection(notificationsActivity.getResources().getConfiguration().getLayoutDirection()); + chip.setText(text); + chip.setChipIconResource(R.drawable.accent_circle); + chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight()); + + return chip; + } + public void removeNotification(NotificationViewHolder holder) { int position = holder.getAdapterPosition(); @@ -360,6 +407,16 @@ public int getItemCount() { return notificationsList.size(); } + @Override + public void avatarGenerated(Drawable avatarDrawable, Object callContext) { + ((ChipDrawable) callContext).setChipIcon(avatarDrawable); + } + + @Override + public boolean shouldCallGeneratedCallback(String tag, Object callContext) { + return true; + } + public static class NotificationViewHolder extends RecyclerView.ViewHolder { NotificationListItemBinding binding; diff --git a/app/src/main/res/layout/notification_list_item.xml b/app/src/main/res/layout/notification_list_item.xml index 1419aafd5b92..ff0150aaa522 100644 --- a/app/src/main/res/layout/notification_list_item.xml +++ b/app/src/main/res/layout/notification_list_item.xml @@ -37,9 +37,10 @@