Fix overlapping date and unread decorations.

This commit is contained in:
Cody Henthorne 2023-07-14 11:06:44 -04:00 committed by Nicholas Tinsley
parent f6bbb59400
commit daf077b3c9
4 changed files with 84 additions and 133 deletions

View file

@ -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()

View file

@ -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<Long, DateHeaderViewHolder> = 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<ConversationElement?> = 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))
}
}
}
}

View file

@ -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))
}
}
}
}

View file

@ -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()