From 2e54324ea4777a3646d90b51dc0748b0352cebcd Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 30 Jul 2026 11:42:09 +0200 Subject: [PATCH 1/3] wip Signed-off-by: alperozturk96 --- .../ui/adapter/NotificationListAdapter.kt | 53 +++++++++++++------ .../notifications/NotificationsFragment.kt | 4 +- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt index 2a8dbf915ebb..bf31a62a7244 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt @@ -31,11 +31,13 @@ import androidx.core.view.size import androidx.recyclerview.widget.RecyclerView import com.google.android.material.button.MaterialButton import com.nextcloud.android.common.ui.theme.utils.ColorRole +import com.nextcloud.client.account.CurrentAccountProvider import com.nextcloud.utils.extensions.setVisibleIf import com.owncloud.android.R import com.owncloud.android.databinding.NotificationListItemBinding import com.owncloud.android.lib.resources.notifications.models.Action import com.owncloud.android.lib.resources.notifications.models.Notification +import com.owncloud.android.ui.activities.adapter.ActivityListAdapter import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.fragment.notifications.NotificationsAdapterItemClick import com.owncloud.android.ui.fragment.notifications.NotificationsFragment @@ -46,7 +48,8 @@ import com.owncloud.android.utils.theme.ViewThemeUtils class NotificationListAdapter( private val fragment: NotificationsFragment, private val viewThemeUtils: ViewThemeUtils, - private val itemClick: NotificationsAdapterItemClick + private val itemClick: NotificationsAdapterItemClick, + private val accountManager: CurrentAccountProvider ) : RecyclerView.Adapter() { private val styleSpanBold = StyleSpan(Typeface.BOLD) @@ -301,25 +304,42 @@ class NotificationListAdapter( } // endregion - private fun makeSpecialPartsBold(notification: Notification): SpannableStringBuilder { - var text = notification.getSubjectRich() + private fun makeSpecialPartsBold(richElement: Notification): SpannableStringBuilder { + var text = richElement.getSubjectRich() val ssb = SpannableStringBuilder(text) - var openingBrace = text.indexOf('{') - var closingBrace: Int - var replaceablePart: String? - while (openingBrace != -1) { - closingBrace = text.indexOf('}', openingBrace) + 1 - replaceablePart = text.substring(openingBrace + 1, closingBrace - 1) - notification.subjectRichParameters[replaceablePart]?.name?.let { name -> - ssb.replace(openingBrace, closingBrace, name) + var start = text.indexOf(PLACEHOLDER_START) + var end: Int + var tag: String? + while (start != -1) { + end = text.indexOf(PLACEHOLDER_END, start) + 1 + tag = text.substring(start + 1, end - 1) + val richObject = richElement.subjectRichParameters[tag] + + val nextSearchStart = when { + richObject == null -> end + + richObject.type == USER_TYPE -> { + ssb.applyMentionSpan(richObject, start, end) + end + } + + else -> { + val nameEnd = ssb.applyClickableNameSpan(richObject, start, end) + text = ssb.toString() + nameEnd + } + } + + richObject?.name?.let { name -> + ssb.replace(start, end, name) text = ssb.toString() - closingBrace = openingBrace + name.length + end = start + name.length - ssb.setSpan(styleSpanBold, openingBrace, closingBrace, 0) - ssb.setSpan(foregroundColorSpanBlack, openingBrace, closingBrace, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) + ssb.setSpan(styleSpanBold, start, end, 0) + ssb.setSpan(foregroundColorSpanBlack, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } - openingBrace = text.indexOf('{', closingBrace) + start = text.indexOf(PLACEHOLDER_START, nextSearchStart) } return ssb @@ -331,5 +351,8 @@ class NotificationListAdapter( companion object { private const val FILE = "file" private const val ACTION_TYPE_WEB = "WEB" + private const val PLACEHOLDER_START = '{' + private const val PLACEHOLDER_END = '}' + private const val USER_TYPE = "user" } } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/notifications/NotificationsFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/notifications/NotificationsFragment.kt index 43641f933d6e..0a9581cb478c 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/notifications/NotificationsFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/notifications/NotificationsFragment.kt @@ -302,7 +302,7 @@ class NotificationsFragment : private fun initializeAdapter() { if (adapter == null) { - adapter = NotificationListAdapter(this@NotificationsFragment, viewThemeUtils, this) + adapter = NotificationListAdapter(this@NotificationsFragment, viewThemeUtils, this, accountManager) binding?.list?.adapter = adapter } } @@ -315,7 +315,7 @@ class NotificationsFragment : @VisibleForTesting fun initForTesting(state: NotificationsUIState) { - adapter = NotificationListAdapter(this@NotificationsFragment, viewThemeUtils, this) + adapter = NotificationListAdapter(this@NotificationsFragment, viewThemeUtils, this, accountManager) binding?.list?.adapter = adapter binding?.list?.layoutManager = LinearLayoutManager(requireContext()) this.state = state From 7d3c27a7061342f0576882febb9a84383e036146 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 30 Jul 2026 12:02:21 +0200 Subject: [PATCH 2/3] feat(chip): activities list adapter, notification list adapter Signed-off-by: alperozturk96 --- .../utils/text/RichSubjectFormatter.kt | 132 ++++++++++++++++++ .../nextcloud/utils/text/RichSubjectParam.kt | 16 +++ .../activities/adapter/ActivityListAdapter.kt | 118 ++-------------- .../ui/adapter/NotificationListAdapter.kt | 92 ++++-------- 4 files changed, 189 insertions(+), 169 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/text/RichSubjectFormatter.kt create mode 100644 app/src/main/java/com/nextcloud/utils/text/RichSubjectParam.kt diff --git a/app/src/main/java/com/nextcloud/utils/text/RichSubjectFormatter.kt b/app/src/main/java/com/nextcloud/utils/text/RichSubjectFormatter.kt new file mode 100644 index 000000000000..b7d3684bb453 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/text/RichSubjectFormatter.kt @@ -0,0 +1,132 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.utils.text + +import android.content.Context +import android.graphics.Typeface +import android.graphics.drawable.Drawable +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.TextPaint +import android.text.TextUtils +import android.text.style.ClickableSpan +import android.text.style.ForegroundColorSpan +import android.text.style.StyleSpan +import android.view.View +import androidx.core.content.ContextCompat +import com.google.android.material.chip.ChipDrawable +import com.nextcloud.client.account.CurrentAccountProvider +import com.owncloud.android.R +import com.owncloud.android.utils.DisplayUtils +import thirdparties.fresco.BetterImageSpan + +class RichSubjectFormatter(private val context: Context, private val currentAccountProvider: CurrentAccountProvider) : + DisplayUtils.AvatarGenerationListener { + + fun format(richSubject: String, paramForTag: (String) -> RichSubjectParam?): SpannableStringBuilder { + var text = richSubject + val ssb = SpannableStringBuilder(text) + + var start = text.indexOf(PLACEHOLDER_START) + while (start != -1) { + val end = text.indexOf(PLACEHOLDER_END, start) + 1 + if (end <= start) break + + val param = paramForTag(text.substring(start + 1, end - 1)) + + val nextSearchStart = when { + param == null -> end + + param.isMention -> { + ssb.applyMentionSpan(param, start, end) + end + } + + else -> { + val nameEnd = ssb.applyClickableNameSpan(param, start, end) + text = ssb.toString() + nameEnd + } + } + + start = text.indexOf(PLACEHOLDER_START, nextSearchStart) + } + + return ssb + } + + override fun avatarGenerated(avatarDrawable: Drawable, callContext: Any) { + (callContext as? ChipDrawable)?.chipIcon = avatarDrawable + } + + override fun shouldCallGeneratedCallback(tag: String, callContext: Any): Boolean = true + + private fun SpannableStringBuilder.applyMentionSpan(param: RichSubjectParam, start: Int, end: Int) { + val name = param.name + val chip = mentionChipDrawable(name.orEmpty()) + val span = MentionChipSpan(chip, BetterImageSpan.ALIGN_CENTER, param.id.orEmpty(), name) + + param.id?.let { id -> + DisplayUtils.setAvatar( + currentAccountProvider.user, + id, + name, + this@RichSubjectFormatter, + context.resources.getDimension(R.dimen.avatar_icon_radius), + context.resources, + chip, + context + ) + } + + setSpan(span, start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) + } + + private fun SpannableStringBuilder.applyClickableNameSpan(param: RichSubjectParam, start: Int, end: Int): Int { + val name = param.name.orEmpty() + replace(start, end, name) + val nameEnd = start + name.length + + param.onClick?.let { onClick -> + setSpan( + object : ClickableSpan() { + override fun onClick(widget: View) = onClick() + override fun updateDrawState(ds: TextPaint) { + ds.isUnderlineText = false + } + }, + start, + nameEnd, + 0 + ) + } + + setSpan(StyleSpan(Typeface.BOLD), start, nameEnd, 0) + setSpan( + ForegroundColorSpan(ContextCompat.getColor(context, R.color.text_color)), + start, + nameEnd, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + + return nameEnd + } + + private fun mentionChipDrawable(text: String): ChipDrawable = + ChipDrawable.createFromResource(context, R.xml.chip_others).apply { + setEllipsize(TextUtils.TruncateAt.MIDDLE) + layoutDirection = context.resources.configuration.layoutDirection + setText(text) + setChipIconResource(R.drawable.accent_circle) + setBounds(0, 0, intrinsicWidth, intrinsicHeight) + } + + companion object { + private const val PLACEHOLDER_START = '{' + private const val PLACEHOLDER_END = '}' + } +} diff --git a/app/src/main/java/com/nextcloud/utils/text/RichSubjectParam.kt b/app/src/main/java/com/nextcloud/utils/text/RichSubjectParam.kt new file mode 100644 index 000000000000..543b478076e9 --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/text/RichSubjectParam.kt @@ -0,0 +1,16 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.utils.text + +data class RichSubjectParam(val type: String?, val id: String?, val name: String?, val onClick: (() -> Unit)? = null) { + val isMention: Boolean + get() = type == USER_TYPE + + companion object { + private const val USER_TYPE = "user" + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt index c683fb31be2b..4aa0874ba9db 100644 --- a/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/activities/adapter/ActivityListAdapter.kt @@ -7,17 +7,9 @@ package com.owncloud.android.ui.activities.adapter import android.content.Context -import android.graphics.Typeface -import android.graphics.drawable.Drawable -import android.text.Spannable import android.text.SpannableStringBuilder -import android.text.TextPaint -import android.text.TextUtils import android.text.format.DateFormat import android.text.format.DateUtils -import android.text.style.ClickableSpan -import android.text.style.ForegroundColorSpan -import android.text.style.StyleSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -29,12 +21,12 @@ import androidx.annotation.DrawableRes import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView -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.MentionChipSpan +import com.nextcloud.utils.text.RichSubjectFormatter +import com.nextcloud.utils.text.RichSubjectParam import com.owncloud.android.MainApp import com.owncloud.android.R import com.owncloud.android.databinding.ActivityListItemBinding @@ -53,7 +45,6 @@ import com.owncloud.android.utils.theme.ViewThemeUtils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import thirdparties.fresco.BetterImageSpan import java.util.Locale import kotlin.math.floor import kotlin.math.log @@ -67,13 +58,13 @@ open class ActivityListAdapter( private val isDetailView: Boolean, private val viewThemeUtils: ViewThemeUtils ) : RecyclerView.Adapter(), - StickyHeaderAdapter, - DisplayUtils.AvatarGenerationListener { + StickyHeaderAdapter { protected var client: NextcloudClient? = null val values: MutableList = mutableListOf() private val px = getThumbnailDimension() private var cachedNextcloudClient: NextcloudClient? = null + private val richSubjectFormatter by lazy { RichSubjectFormatter(context, currentAccountProvider) } // region Public Methods @Suppress("NotifyDataSetChanged") @@ -138,12 +129,6 @@ open class ActivityListAdapter( override fun isHeader(itemPosition: Int) = itemPosition in values.indices && getItemViewType(itemPosition) == HEADER_TYPE - - override fun avatarGenerated(avatarDrawable: Drawable, callContext: Any) { - (callContext as ChipDrawable).chipIcon = avatarDrawable - } - - override fun shouldCallGeneratedCallback(tag: String, callContext: Any): Boolean = true // endregion // region Private Methods @@ -248,15 +233,6 @@ open class ActivityListAdapter( } } - private fun getDrawableForMentionChipSpan(chipResource: Int, text: String): ChipDrawable = - ChipDrawable.createFromResource(context, chipResource).apply { - setEllipsize(TextUtils.TruncateAt.MIDDLE) - layoutDirection = context.resources.configuration.layoutDirection - setText(text) - setChipIconResource(R.drawable.accent_circle) - setBounds(0, 0, intrinsicWidth, intrinsicHeight) - } - private suspend fun nextcloudClient(): NextcloudClient = cachedNextcloudClient ?: withContext(Dispatchers.IO) { OwnCloudClientManagerFactory.getDefaultSingleton() .getNextcloudClientFor(currentAccountProvider.user.toOwnCloudAccount(), context) @@ -302,83 +278,16 @@ open class ActivityListAdapter( return imageView } - private fun addClickablePart(richElement: RichElement): SpannableStringBuilder { - var text = richElement.richSubject - val ssb = SpannableStringBuilder(text) - - var start = text.indexOf(PLACEHOLDER_START) - while (start != -1) { - val end = text.indexOf(PLACEHOLDER_END, start) + 1 - val tag = text.substring(start + 1, end - 1) - val richObject = richElement.richObjectList.firstOrNull { it.tag.equals(tag, ignoreCase = true) } - - val nextSearchStart = when { - richObject == null -> end - - richObject.type == USER_TYPE -> { - ssb.applyMentionSpan(richObject, start, end) - end - } - - else -> { - val nameEnd = ssb.applyClickableNameSpan(richObject, start, end) - text = ssb.toString() - nameEnd - } - } - - start = text.indexOf(PLACEHOLDER_START, nextSearchStart) - } - - return ssb - } - - private fun SpannableStringBuilder.applyMentionSpan(richObject: RichObject, start: Int, end: Int) { - val name = richObject.name - val chip = getDrawableForMentionChipSpan(R.xml.chip_others, name ?: "") - val span = MentionChipSpan(chip, BetterImageSpan.ALIGN_CENTER, richObject.id ?: "", name) - - richObject.id?.let { id -> - DisplayUtils.setAvatar( - currentAccountProvider.user, - id, - name, - this@ActivityListAdapter, - context.resources.getDimension(R.dimen.avatar_icon_radius), - context.resources, - chip, - context - ) + private fun addClickablePart(richElement: RichElement): SpannableStringBuilder = + richSubjectFormatter.format(richElement.richSubject) { tag -> + richElement.richObjectList + .firstOrNull { it.tag.equals(tag, ignoreCase = true) } + ?.toRichSubjectParam() } - setSpan(span, start, end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) - } - - private fun SpannableStringBuilder.applyClickableNameSpan(richObject: RichObject, start: Int, end: Int): Int { - val name = richObject.name.orEmpty() - replace(start, end, name) - val nameEnd = start + name.length - - setSpan( - object : ClickableSpan() { - override fun onClick(widget: View) = activityListInterface.onActivityClicked(richObject) - override fun updateDrawState(ds: TextPaint) { - ds.isUnderlineText = false - } - }, - start, - nameEnd, - 0 - ) - setSpan(StyleSpan(Typeface.BOLD), start, nameEnd, 0) - setSpan( - ForegroundColorSpan(context.resources.getColor(R.color.text_color)), - start, - nameEnd, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - ) - - return nameEnd + private fun RichObject.toRichSubjectParam(): RichSubjectParam { + val richObject = this + return RichSubjectParam(type, id, name) { activityListInterface.onActivityClicked(richObject) } } private fun getThumbnailDimension(): Int { @@ -397,9 +306,6 @@ open class ActivityListAdapter( const val HEADER_TYPE = 100 const val ACTIVITY_TYPE = 101 private const val COLORED_ICON_SUFFIX = "-color.svg" - private const val PLACEHOLDER_START = '{' - private const val PLACEHOLDER_END = '}' - private const val USER_TYPE = "user" private const val TIME_PATTERN = "HH:mm" private const val HEADER_DATE_SKELETON = "EEEE, MMMM d" private const val PREVIEW_COLUMN_SPACING = 20 diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt index bf31a62a7244..5f15760ee025 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/NotificationListAdapter.kt @@ -14,17 +14,13 @@ package com.owncloud.android.ui.adapter import android.annotation.SuppressLint import android.content.Intent import android.graphics.Typeface -import android.text.Spannable import android.text.SpannableStringBuilder -import android.text.style.ForegroundColorSpan -import android.text.style.StyleSpan import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import androidx.appcompat.widget.PopupMenu -import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat import androidx.core.net.toUri import androidx.core.view.size @@ -33,11 +29,13 @@ import com.google.android.material.button.MaterialButton import com.nextcloud.android.common.ui.theme.utils.ColorRole import com.nextcloud.client.account.CurrentAccountProvider import com.nextcloud.utils.extensions.setVisibleIf +import com.nextcloud.utils.text.RichSubjectFormatter +import com.nextcloud.utils.text.RichSubjectParam import com.owncloud.android.R import com.owncloud.android.databinding.NotificationListItemBinding import com.owncloud.android.lib.resources.notifications.models.Action import com.owncloud.android.lib.resources.notifications.models.Notification -import com.owncloud.android.ui.activities.adapter.ActivityListAdapter +import com.owncloud.android.lib.resources.notifications.models.RichObject import com.owncloud.android.ui.activity.FileDisplayActivity import com.owncloud.android.ui.fragment.notifications.NotificationsAdapterItemClick import com.owncloud.android.ui.fragment.notifications.NotificationsFragment @@ -52,11 +50,10 @@ class NotificationListAdapter( private val accountManager: CurrentAccountProvider ) : RecyclerView.Adapter() { - private val styleSpanBold = StyleSpan(Typeface.BOLD) - private val foregroundColorSpanBlack = ForegroundColorSpan( - ContextCompat.getColor(fragment.requireContext(), R.color.text_color) - ) private val notificationsList = ArrayList() + private val richSubjectFormatter by lazy { + RichSubjectFormatter(fragment.requireContext(), accountManager) + } // region Adapter overrides override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = NotificationViewHolder( @@ -105,23 +102,36 @@ class NotificationListAdapter( } else { holder.binding.subject.run { text = if (!notification.subjectRich.isNullOrEmpty()) { - makeSpecialPartsBold(notification) + formatSubjectRich(notification) } else { notification.getSubject() } - if (file?.id?.isNotEmpty() == true) { - setOnClickListener { - val intent = Intent(fragment.requireActivity(), FileDisplayActivity::class.java).apply { - action = Intent.ACTION_VIEW - putExtra(FileDisplayActivity.KEY_FILE_ID, file.id) - } - fragment.requireActivity().startActivity(intent) - } - } + val fileId = file?.id?.takeIf { it.isNotEmpty() } + setOnClickListener(fileId?.let { id -> View.OnClickListener { showFile(id) } }) } } } + private fun formatSubjectRich(notification: Notification): SpannableStringBuilder = + richSubjectFormatter.format(notification.getSubjectRich()) { tag -> + notification.subjectRichParameters[tag]?.toRichSubjectParam() + } + + private fun RichObject.toRichSubjectParam(): RichSubjectParam { + val fileId = id?.takeIf { type == FILE && it.isNotEmpty() } + ?: return RichSubjectParam(type, id, name) + + return RichSubjectParam(type, id, name) { showFile(fileId) } + } + + private fun showFile(fileId: String) { + val intent = Intent(fragment.requireActivity(), FileDisplayActivity::class.java).apply { + action = Intent.ACTION_VIEW + putExtra(FileDisplayActivity.KEY_FILE_ID, fileId) + } + fragment.requireActivity().startActivity(intent) + } + private fun bindMessage(holder: NotificationViewHolder, notification: Notification) { val message = notification.getMessage() holder.binding.message.run { @@ -304,55 +314,11 @@ class NotificationListAdapter( } // endregion - private fun makeSpecialPartsBold(richElement: Notification): SpannableStringBuilder { - var text = richElement.getSubjectRich() - val ssb = SpannableStringBuilder(text) - - var start = text.indexOf(PLACEHOLDER_START) - var end: Int - var tag: String? - while (start != -1) { - end = text.indexOf(PLACEHOLDER_END, start) + 1 - tag = text.substring(start + 1, end - 1) - val richObject = richElement.subjectRichParameters[tag] - - val nextSearchStart = when { - richObject == null -> end - - richObject.type == USER_TYPE -> { - ssb.applyMentionSpan(richObject, start, end) - end - } - - else -> { - val nameEnd = ssb.applyClickableNameSpan(richObject, start, end) - text = ssb.toString() - nameEnd - } - } - - richObject?.name?.let { name -> - ssb.replace(start, end, name) - text = ssb.toString() - end = start + name.length - - ssb.setSpan(styleSpanBold, start, end, 0) - ssb.setSpan(foregroundColorSpanBlack, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - } - start = text.indexOf(PLACEHOLDER_START, nextSearchStart) - } - - return ssb - } - class NotificationViewHolder(var binding: NotificationListItemBinding) : RecyclerView.ViewHolder(binding.root) companion object { private const val FILE = "file" private const val ACTION_TYPE_WEB = "WEB" - private const val PLACEHOLDER_START = '{' - private const val PLACEHOLDER_END = '}' - private const val USER_TYPE = "user" } } From 88b8010e3a8b1e8a2ba556cacaeb851ea8f358ed Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 30 Jul 2026 12:34:13 +0200 Subject: [PATCH 3/3] fix alignment Signed-off-by: alperozturk96 --- app/src/main/java/thirdparties/fresco/BetterImageSpan.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/thirdparties/fresco/BetterImageSpan.kt b/app/src/main/java/thirdparties/fresco/BetterImageSpan.kt index 17c23bfaf64d..f5c063163d38 100644 --- a/app/src/main/java/thirdparties/fresco/BetterImageSpan.kt +++ b/app/src/main/java/thirdparties/fresco/BetterImageSpan.kt @@ -52,7 +52,8 @@ open class BetterImageSpan @JvmOverloads constructor( return width } - val offsetAbove = getOffsetAboveBaseline(fontMetrics) + paint.getFontMetricsInt(fontMetricsInt) + val offsetAbove = getOffsetAboveBaseline(fontMetricsInt) val offsetBelow = height + offsetAbove if (offsetAbove < fontMetrics.ascent) { fontMetrics.ascent = offsetAbove