diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt index 62ff3a34d4..74d3659ba5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationFragment.kt @@ -433,8 +433,7 @@ class ConversationFragment : private lateinit var multiselectItemDecoration: MultiselectItemDecoration private lateinit var openableGiftItemDecoration: OpenableGiftItemDecoration private lateinit var threadHeaderMarginDecoration: ThreadHeaderMarginDecoration - private lateinit var dateHeaderDecoration: DateHeaderDecoration - private lateinit var unreadLineDecoration: UnreadLineDecoration + private lateinit var conversationItemDecorations: ConversationItemDecorations private lateinit var optionsMenuCallback: ConversationOptionsMenuCallback private lateinit var typingIndicatorDecoration: TypingIndicatorDecoration @@ -571,7 +570,7 @@ class ConversationFragment : inputPanel.onPause() - unreadLineDecoration.unreadCount = viewModel.unreadCount + conversationItemDecorations.unreadCount = viewModel.unreadCount binding.conversationItemRecycler.invalidateItemDecorations() viewModel.markLastSeen() @@ -747,7 +746,7 @@ class ConversationFragment : binding.conversationItemRecycler.height ) } - unreadLineDecoration.unreadCount = state.meta.unreadCount + conversationItemDecorations.unreadCount = state.meta.unreadCount } .flatMapObservable { it.items.data } .observeOn(AndroidSchedulers.mainThread()) @@ -758,7 +757,7 @@ class ConversationFragment : adapter.submitList(it) { scrollToPositionDelegate.notifyListCommitted() - dateHeaderDecoration.currentItems = it + conversationItemDecorations.currentItems = it if (firstRender) { firstRender = false @@ -1176,8 +1175,7 @@ class ConversationFragment : inputPanel.setWallpaperEnabled(wallpaperEnabled) adapter.onHasWallpaperChanged(wallpaperEnabled) - dateHeaderDecoration.hasWallpaper = wallpaperEnabled - unreadLineDecoration.hasWallpaper = wallpaperEnabled + conversationItemDecorations.hasWallpaper = wallpaperEnabled val navColor = if (wallpaperEnabled) { R.color.conversation_navigation_wallpaper @@ -1411,11 +1409,8 @@ class ConversationFragment : threadHeaderMarginDecoration = ThreadHeaderMarginDecoration() binding.conversationItemRecycler.addItemDecoration(threadHeaderMarginDecoration) - dateHeaderDecoration = DateHeaderDecoration(hasWallpaper = args.wallpaper != null) - binding.conversationItemRecycler.addItemDecoration(dateHeaderDecoration, 0) - - unreadLineDecoration = UnreadLineDecoration(hasWallpaper = args.wallpaper != null) - binding.conversationItemRecycler.addItemDecoration(unreadLineDecoration) + conversationItemDecorations = ConversationItemDecorations(hasWallpaper = args.wallpaper != null) + binding.conversationItemRecycler.addItemDecoration(conversationItemDecorations, 0) } private fun initializeGiphyMp4(): GiphyMp4ProjectionRecycler { @@ -1679,7 +1674,7 @@ class ConversationFragment : return } - unreadLineDecoration.unreadCount = 0 + conversationItemDecorations.unreadCount = 0 scrollToPositionDelegate.resetScrollPosition() attachmentManager.cleanup() diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DateHeaderDecoration.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations.kt similarity index 63% rename from app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DateHeaderDecoration.kt rename to app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations.kt index ff0765aa47..c6f6749fd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/DateHeaderDecoration.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationItemDecorations.kt @@ -17,7 +17,7 @@ import org.thoughtcrime.securesms.conversation.v2.data.ConversationMessageElemen import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord import org.thoughtcrime.securesms.util.DateUtils import org.thoughtcrime.securesms.util.adapter.mapping.MappingModel -import org.thoughtcrime.securesms.util.drawAsItemDecoration +import org.thoughtcrime.securesms.util.drawAsTopItemDecoration import org.thoughtcrime.securesms.util.layoutIn import org.thoughtcrime.securesms.util.toLocalDate import java.util.Locale @@ -30,9 +30,19 @@ private typealias ConversationElement = MappingModel<*> * * This is a converted and trimmed down version of [org.thoughtcrime.securesms.util.StickyHeaderDecoration]. */ -class DateHeaderDecoration(hasWallpaper: Boolean = false, private val scheduleMessageMode: Boolean = false) : RecyclerView.ItemDecoration() { +class ConversationItemDecorations(hasWallpaper: Boolean = false, private val scheduleMessageMode: Boolean = false) : RecyclerView.ItemDecoration() { private val headerCache: MutableMap = hashMapOf() + private var unreadViewHolder: UnreadViewHolder? = null + + var unreadCount: Int = 0 + set(value) { + field = value + unreadViewHolder?.bind() + } + + private val firstUnreadPosition: Int + get() = unreadCount - 1 var currentItems: MutableList = mutableListOf() @@ -40,29 +50,44 @@ class DateHeaderDecoration(hasWallpaper: Boolean = false, private val scheduleMe set(value) { field = value headerCache.values.forEach { it.updateForWallpaper() } + unreadViewHolder?.updateForWallpaper() } override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { val position = parent.getChildAdapterPosition(view) - val headerHeight = if (position in currentItems.indices && hasHeader(position)) { + val unreadHeight = if (unreadCount > 0 && position == firstUnreadPosition) { + getUnreadViewHolder(parent).height + } else { + 0 + } + + val dateHeaderHeight = if (position in currentItems.indices && hasHeader(position)) { getHeader(parent, currentItems[position] as ConversationMessageElement).height } else { 0 } - outRect.set(0, headerHeight, 0, 0) + outRect.set(0, unreadHeight + dateHeaderHeight, 0, 0) } override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { val count = parent.childCount for (layoutPosition in 0 until count) { - val child = parent.getChildAt(parent.childCount - 1 - layoutPosition) + val child = parent.getChildAt(count - 1 - layoutPosition) val position = parent.getChildAdapterPosition(child) + val unreadOffset = if (position == firstUnreadPosition) { + val unread = getUnreadViewHolder(parent) + unread.itemView.drawAsTopItemDecoration(c, parent, child) + unread.height + } else { + 0 + } + if (hasHeader(position)) { val headerView = getHeader(parent, currentItems[position] as ConversationMessageElement).itemView - headerView.drawAsItemDecoration(c, parent, child) + headerView.drawAsTopItemDecoration(c, parent, child, unreadOffset) } } } @@ -103,6 +128,15 @@ class DateHeaderDecoration(hasWallpaper: Boolean = false, private val scheduleMe return headerHolder } + private fun getUnreadViewHolder(parent: RecyclerView): UnreadViewHolder { + if (unreadViewHolder != null) { + return unreadViewHolder!! + } + + unreadViewHolder = UnreadViewHolder(parent) + return unreadViewHolder!! + } + private fun ConversationMessageElement.timestamp(): Long { return if (scheduleMessageMode) { (conversationMessage.messageRecord as MediaMmsMessageRecord).scheduledDate @@ -137,4 +171,38 @@ class DateHeaderDecoration(hasWallpaper: Boolean = false, private val scheduleMe } } } + + private inner class UnreadViewHolder(parent: RecyclerView) { + val itemView: View + + private val unreadText: TextView + private val unreadDivider: View + + val height: Int + get() = itemView.height + + init { + itemView = LayoutInflater.from(parent.context).inflate(R.layout.conversation_item_last_seen, parent, false) + unreadText = itemView.findViewById(R.id.text) + unreadDivider = itemView.findViewById(R.id.last_seen_divider) + + bind() + itemView.layoutIn(parent) + } + + fun bind() { + unreadText.text = itemView.context.resources.getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, unreadCount, unreadCount) + updateForWallpaper() + } + + fun updateForWallpaper() { + if (hasWallpaper) { + unreadText.setBackgroundResource(R.drawable.wallpaper_bubble_background_18) + unreadDivider.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.transparent_black_80)) + } else { + unreadText.background = null + unreadDivider.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.core_grey_45)) + } + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/UnreadLineDecoration.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/UnreadLineDecoration.kt deleted file mode 100644 index 567a225471..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/UnreadLineDecoration.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2023 Signal Messenger, LLC - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package org.thoughtcrime.securesms.conversation.v2 - -import android.graphics.Canvas -import android.graphics.Rect -import android.view.LayoutInflater -import android.view.View -import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.RecyclerView -import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.util.drawAsItemDecoration -import org.thoughtcrime.securesms.util.layoutIn - -/** - * Renders the unread divider in a conversation list based on the unread count. - */ -class UnreadLineDecoration(hasWallpaper: Boolean) : RecyclerView.ItemDecoration() { - - private var unreadViewHolder: UnreadViewHolder? = null - - var unreadCount: Int = 0 - set(value) { - field = value - unreadViewHolder?.bind() - } - - private val firstUnreadPosition: Int - get() = unreadCount - 1 - - var hasWallpaper: Boolean = hasWallpaper - set(value) { - field = value - unreadViewHolder?.updateForWallpaper() - } - - override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { - if (unreadCount == 0) { - super.getItemOffsets(outRect, view, parent, state) - return - } - - val position = parent.getChildAdapterPosition(view) - - val height = if (position == firstUnreadPosition) { - getUnreadViewHolder(parent).height - } else { - 0 - } - - outRect.set(0, height, 0, 0) - } - - override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - for (layoutPosition in 0 until parent.childCount) { - val child = parent.getChildAt(layoutPosition) - val position = parent.getChildAdapterPosition(child) - - if (position == firstUnreadPosition) { - getUnreadViewHolder(parent).itemView.drawAsItemDecoration(c, parent, child) - break - } - } - } - - private fun getUnreadViewHolder(parent: RecyclerView): UnreadViewHolder { - if (unreadViewHolder != null) { - return unreadViewHolder!! - } - - unreadViewHolder = UnreadViewHolder(parent) - return unreadViewHolder!! - } - - private inner class UnreadViewHolder(parent: RecyclerView) { - val itemView: View - - private val unreadText: TextView - private val unreadDivider: View - - val height: Int - get() = itemView.height - - init { - itemView = LayoutInflater.from(parent.context).inflate(R.layout.conversation_item_last_seen, parent, false) - unreadText = itemView.findViewById(R.id.text) - unreadDivider = itemView.findViewById(R.id.last_seen_divider) - - bind() - itemView.layoutIn(parent) - } - - fun bind() { - unreadText.text = itemView.context.resources.getQuantityString(R.plurals.ConversationAdapter_n_unread_messages, unreadCount, unreadCount) - updateForWallpaper() - } - - fun updateForWallpaper() { - if (hasWallpaper) { - unreadText.setBackgroundResource(R.drawable.wallpaper_bubble_background_18) - unreadDivider.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.transparent_black_80)) - } else { - unreadText.background = null - unreadDivider.setBackgroundColor(ContextCompat.getColor(itemView.context, R.color.core_grey_45)) - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ViewExtensions.kt b/app/src/main/java/org/thoughtcrime/securesms/util/ViewExtensions.kt index d684e32410..65f4a7b063 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/ViewExtensions.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ViewExtensions.kt @@ -83,10 +83,10 @@ fun View.layoutIn(parent: View) { layout(0, 0, measuredWidth, measuredHeight) } -fun View.drawAsItemDecoration(canvas: Canvas, parent: View, child: View) { +fun View.drawAsTopItemDecoration(canvas: Canvas, parent: View, child: View, offset: Int = 0) { canvas.save() val left = parent.left - val top = child.y.toInt() - height + val top = child.y.toInt() - height - offset canvas.translate(left.toFloat(), top.toFloat()) draw(canvas) canvas.restore()