From 7ad1bc5c8dcc43d7c8470dcc9846c4c389e03ad6 Mon Sep 17 00:00:00 2001 From: tobiasKaminsky Date: Wed, 5 Mar 2025 09:14:07 +0100 Subject: [PATCH] Show avatars in activity list Signed-off-by: tobiasKaminsky --- .../java/com/nextcloud/utils/text/Spans.java | 82 ++++++++++++ .../ui/adapter/ActivityListAdapter.java | 103 +++++++++++---- .../third_parties/fresco/BetterImageSpan.kt | 117 ++++++++++++++++++ app/src/main/res/drawable/accent_circle.xml | 10 ++ .../main/res/layout/activity_list_item.xml | 6 +- app/src/main/res/values/dims.xml | 1 + app/src/main/res/values/styles.xml | 4 + app/src/main/res/xml/chip_others.xml | 13 ++ 8 files changed, 307 insertions(+), 29 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/text/Spans.java create mode 100644 app/src/main/java/third_parties/fresco/BetterImageSpan.kt create mode 100644 app/src/main/res/drawable/accent_circle.xml create mode 100644 app/src/main/res/xml/chip_others.xml diff --git a/app/src/main/java/com/nextcloud/utils/text/Spans.java b/app/src/main/java/com/nextcloud/utils/text/Spans.java new file mode 100644 index 000000000000..098366292ccb --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/text/Spans.java @@ -0,0 +1,82 @@ +/* + * Nextcloud Talk - Android Client + * + * SPDX-FileCopyrightText: 2021 Andy Scherzinger + * SPDX-FileCopyrightText: 2017-2018 Mario Danic + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package com.nextcloud.utils.text; + +import android.graphics.drawable.Drawable; + +import androidx.annotation.NonNull; +import thirdparties.fresco.BetterImageSpan; + +public class Spans { + + public static class MentionChipSpan extends BetterImageSpan { + public String id; + public CharSequence label; + + public MentionChipSpan(@NonNull Drawable drawable, int verticalAlignment, String id, CharSequence label) { + super(drawable, verticalAlignment); + this.id = id; + this.label = label; + } + + public String getId() { + return this.id; + } + + public CharSequence getLabel() { + return this.label; + } + + public void setId(String id) { + this.id = id; + } + + public void setLabel(CharSequence label) { + this.label = label; + } + + public boolean equals(final Object o) { + if (o == this) { + return true; + } + if (!(o instanceof MentionChipSpan)) { + return false; + } + final MentionChipSpan other = (MentionChipSpan) o; + if (!other.canEqual((Object) this)) { + return false; + } + final Object this$id = this.getId(); + final Object other$id = other.getId(); + if (this$id == null ? other$id != null : !this$id.equals(other$id)) { + return false; + } + final Object this$label = this.getLabel(); + final Object other$label = other.getLabel(); + + return this$label == null ? other$label == null : this$label.equals(other$label); + } + + protected boolean canEqual(final Object other) { + return other instanceof MentionChipSpan; + } + + public int hashCode() { + final int PRIME = 59; + int result = 1; + final Object $id = this.getId(); + result = result * PRIME + ($id == null ? 43 : $id.hashCode()); + final Object $label = this.getLabel(); + return result * PRIME + ($label == null ? 43 : $label.hashCode()); + } + + public String toString() { + return "Spans.MentionChipSpan(id=" + this.getId() + ", label=" + this.getLabel() + ")"; + } + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java index d8246088ae81..d159fc33abc6 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/ActivityListAdapter.java @@ -16,6 +16,7 @@ import android.content.res.Resources; import android.graphics.Color; import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.text.Spannable; @@ -24,7 +25,6 @@ import android.text.TextUtils; import android.text.format.DateFormat; import android.text.format.DateUtils; -import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; @@ -35,10 +35,12 @@ import android.widget.LinearLayout; import android.widget.TextView; +import com.google.android.material.chip.ChipDrawable; import com.nextcloud.client.account.CurrentAccountProvider; import com.nextcloud.client.network.ClientFactory; import com.nextcloud.common.NextcloudClient; import com.nextcloud.utils.GlideHelper; +import com.nextcloud.utils.text.Spans; import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.databinding.ActivityListItemBinding; @@ -61,11 +63,13 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import thirdparties.fresco.BetterImageSpan; /** * Adapter for the activity view. */ -public class ActivityListAdapter extends RecyclerView.Adapter implements StickyHeaderAdapter { +public class ActivityListAdapter extends RecyclerView.Adapter implements StickyHeaderAdapter, + DisplayUtils.AvatarGenerationListener { static final int HEADER_TYPE = 100; static final int ACTIVITY_TYPE = 101; @@ -147,9 +151,8 @@ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int positi if (!TextUtils.isEmpty(activity.getRichSubjectElement().getRichSubject())) { activityViewHolder.binding.subject.setVisibility(View.VISIBLE); - activityViewHolder.binding.subject.setMovementMethod(LinkMovementMethod.getInstance()); - activityViewHolder.binding.subject.setText(addClickablePart(activity.getRichSubjectElement()), - TextView.BufferType.SPANNABLE); + activityViewHolder.binding.subject.setText(addClickablePart(activity.getRichSubjectElement())); + activityViewHolder.binding.subject.setVisibility(View.VISIBLE); } else if (!TextUtils.isEmpty(activity.getSubject())) { activityViewHolder.binding.subject.setVisibility(View.VISIBLE); @@ -275,6 +278,17 @@ private ImageView createThumbnailNew(PreviewObject previewObject, List fontMetrics.descent) { + fontMetrics.descent = offsetBelow + } + if (offsetBelow > fontMetrics.bottom) { + fontMetrics.bottom = offsetBelow + } + return mWidth + } + + override fun draw( + canvas: Canvas, + text: CharSequence, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint + ) { + paint.getFontMetricsInt(mFontMetricsInt) + val iconTop = y + getOffsetAboveBaseline(mFontMetricsInt) + canvas.translate(x, iconTop.toFloat()) + drawable.draw(canvas) + canvas.translate(-x, -iconTop.toFloat()) + } + + private fun updateBounds() { + mBounds = drawable.bounds + mWidth = mBounds!!.width() + mHeight = mBounds!!.height() + } + + private fun getOffsetAboveBaseline(fm: Paint.FontMetricsInt): Int = when (mAlignment) { + ALIGN_BOTTOM -> fm.descent - mHeight + ALIGN_CENTER -> { + val textHeight = fm.descent - fm.ascent + val offset = (textHeight - mHeight) / 2 + fm.ascent + offset + } + + ALIGN_BASELINE -> -mHeight + else -> -mHeight + } + + companion object { + const val ALIGN_BOTTOM = 0 + const val ALIGN_BASELINE = 1 + const val ALIGN_CENTER = 2 + } +} diff --git a/app/src/main/res/drawable/accent_circle.xml b/app/src/main/res/drawable/accent_circle.xml new file mode 100644 index 000000000000..3acd7c33c465 --- /dev/null +++ b/app/src/main/res/drawable/accent_circle.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/layout/activity_list_item.xml b/app/src/main/res/layout/activity_list_item.xml index ad17d9e40485..4e0bb51d201c 100644 --- a/app/src/main/res/layout/activity_list_item.xml +++ b/app/src/main/res/layout/activity_list_item.xml @@ -34,11 +34,13 @@ 3dp 16dp 10dp + 12dp 72dp 0dp 56dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 17cdbaabca4a..a3854307c2fd 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -473,4 +473,8 @@ @style/Theme.ownCloud.Launcher + + diff --git a/app/src/main/res/xml/chip_others.xml b/app/src/main/res/xml/chip_others.xml new file mode 100644 index 000000000000..9f878b3edb22 --- /dev/null +++ b/app/src/main/res/xml/chip_others.xml @@ -0,0 +1,13 @@ + +