Add unread divider decoration to CFv2.

This commit is contained in:
Cody Henthorne
2023-07-13 13:00:12 -04:00
committed by Nicholas Tinsley
parent 2511ca17aa
commit 5731bf023a
10 changed files with 176 additions and 32 deletions

View File

@@ -14,7 +14,8 @@ data class ConversationData(
val jumpToPosition: Int,
val threadSize: Int,
val messageRequestData: MessageRequestData,
@get:JvmName("showUniversalExpireTimerMessage") val showUniversalExpireTimerMessage: Boolean
@get:JvmName("showUniversalExpireTimerMessage") val showUniversalExpireTimerMessage: Boolean,
val unreadCount: Int
) {
fun shouldJumpToMessage(): Boolean {

View File

@@ -119,7 +119,7 @@ public class ConversationRepository {
showUniversalExpireTimerUpdate = true;
}
return new ConversationData(conversationRecipient, threadId, lastSeen, lastSeenPosition, lastScrolledPosition, jumpToPosition, threadSize, messageRequestData, showUniversalExpireTimerUpdate);
return new ConversationData(conversationRecipient, threadId, lastSeen, lastSeenPosition, lastScrolledPosition, jumpToPosition, threadSize, messageRequestData, showUniversalExpireTimerUpdate, metadata.getUnreadCount());
}
public void markGiftBadgeRevealed(long messageId) {

View File

@@ -427,6 +427,7 @@ class ConversationFragment :
private lateinit var openableGiftItemDecoration: OpenableGiftItemDecoration
private lateinit var threadHeaderMarginDecoration: ThreadHeaderMarginDecoration
private lateinit var dateHeaderDecoration: DateHeaderDecoration
private lateinit var unreadLineDecoration: UnreadLineDecoration
private lateinit var optionsMenuCallback: ConversationOptionsMenuCallback
private lateinit var typingIndicatorDecoration: TypingIndicatorDecoration
@@ -563,8 +564,10 @@ class ConversationFragment :
inputPanel.onPause()
// todo [cfv2] setLastSeen(System.currentTimeMillis())
// todo [cfv2] markLastSeen()
unreadLineDecoration.unreadCount = viewModel.unreadCount
binding.conversationItemRecycler.invalidateItemDecorations()
viewModel.markLastSeen()
motionEventRelay.setDrain(null)
EventBus.getDefault().unregister(this)
@@ -719,6 +722,7 @@ class ConversationFragment :
binding.conversationItemRecycler.height
)
}
unreadLineDecoration.unreadCount = state.meta.unreadCount
}
.flatMapObservable { it.items.data }
.observeOn(AndroidSchedulers.mainThread())
@@ -748,7 +752,7 @@ class ConversationFragment :
attachmentManager = AttachmentManager(requireContext(), requireView(), AttachmentManagerListener())
EventBus.getDefault().registerForLifecycle(groupCallViewModel, viewLifecycleOwner)
viewLifecycleOwner.lifecycle.addObserver(LastSeenPositionUpdater(adapter, layoutManager, viewModel))
viewLifecycleOwner.lifecycle.addObserver(LastScrolledPositionUpdater(adapter, layoutManager, viewModel))
disposables += viewModel.recipient
.observeOn(AndroidSchedulers.mainThread())
@@ -1147,6 +1151,7 @@ class ConversationFragment :
adapter.onHasWallpaperChanged(wallpaperEnabled)
dateHeaderDecoration.hasWallpaper = wallpaperEnabled
unreadLineDecoration.hasWallpaper = wallpaperEnabled
val navColor = if (wallpaperEnabled) {
R.color.conversation_navigation_wallpaper
@@ -1371,6 +1376,9 @@ class ConversationFragment :
dateHeaderDecoration = DateHeaderDecoration(hasWallpaper = args.wallpaper != null)
binding.conversationItemRecycler.addItemDecoration(dateHeaderDecoration, 0)
unreadLineDecoration = UnreadLineDecoration(hasWallpaper = args.wallpaper != null)
binding.conversationItemRecycler.addItemDecoration(unreadLineDecoration)
}
private fun initializeGiphyMp4(): GiphyMp4ProjectionRecycler {
@@ -1631,7 +1639,7 @@ class ConversationFragment :
return
}
// todo [cfv2] fragment.setLastSeen(0);
unreadLineDecoration.unreadCount = 0
scrollToPositionDelegate.resetScrollPosition()
attachmentManager.cleanup()
@@ -3011,7 +3019,7 @@ class ConversationFragment :
//endregion
private class LastSeenPositionUpdater(
private class LastScrolledPositionUpdater(
val adapter: ConversationAdapterV2,
val layoutManager: LinearLayoutManager,
val viewModel: ConversationViewModel

View File

@@ -258,7 +258,7 @@ class ConversationRepository(
}
fun setLastVisibleMessageTimestamp(threadId: Long, lastVisibleMessageTimestamp: Long) {
SignalExecutors.BOUNDED.submit { SignalDatabase.threads.setLastScrolled(threadId, lastVisibleMessageTimestamp) }
SignalExecutors.BOUNDED_IO.execute { SignalDatabase.threads.setLastScrolled(threadId, lastVisibleMessageTimestamp) }
}
fun markGiftBadgeRevealed(messageId: Long) {
@@ -575,6 +575,12 @@ class ConversationRepository(
}
}
fun markLastSeen(threadId: Long) {
SignalExecutors.BOUNDED_IO.execute {
SignalDatabase.threads.setLastSeen(threadId)
}
}
/**
* Glide target for a contact photo which expects an error drawable, and publishes
* the result to the given emitter.

View File

@@ -88,6 +88,8 @@ class ConversationViewModel(
.observeOn(AndroidSchedulers.mainThread())
val showScrollButtonsSnapshot: Boolean
get() = scrollButtonStateStore.state.showScrollButtons
val unreadCount: Int
get() = scrollButtonStateStore.state.unreadCount
val recipient: Observable<Recipient> = recipientRepository.conversationRecipient
@@ -395,4 +397,8 @@ class ConversationViewModel(
.getScheduledMessageCount(threadId)
.observeOn(AndroidSchedulers.mainThread())
}
fun markLastSeen() {
repository.markLastSeen(threadId)
}
}

View File

@@ -9,7 +9,6 @@ import android.graphics.Canvas
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
@@ -18,6 +17,8 @@ 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.layoutIn
import org.thoughtcrime.securesms.util.toLocalDate
import java.util.Locale
@@ -61,12 +62,7 @@ class DateHeaderDecoration(hasWallpaper: Boolean = false, private val scheduleMe
if (hasHeader(position)) {
val headerView = getHeader(parent, currentItems[position] as ConversationMessageElement).itemView
c.save()
val left = parent.left
val top = child.y.toInt() - headerView.height
c.translate(left.toFloat(), top.toFloat())
headerView.draw(c)
c.restore()
headerView.drawAsItemDecoration(c, parent, child)
}
}
}
@@ -102,13 +98,8 @@ class DateHeaderDecoration(hasWallpaper: Boolean = false, private val scheduleMe
holder
}
val headerView = headerHolder.itemView
val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)
val childWidth = ViewGroup.getChildMeasureSpec(widthSpec, parent.paddingLeft + parent.paddingRight, headerView.layoutParams.width)
val childHeight = ViewGroup.getChildMeasureSpec(heightSpec, parent.paddingTop + parent.paddingBottom, headerView.layoutParams.height)
headerView.measure(childWidth, childHeight)
headerView.layout(0, 0, headerView.measuredWidth, headerView.measuredHeight)
headerHolder.itemView.layoutIn(parent)
return headerHolder
}

View File

@@ -0,0 +1,112 @@
/*
* 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))
}
}
}
}