Rewrite chat colors delegation.

This commit is contained in:
Alex Hart
2024-01-16 15:19:11 -04:00
committed by Greyson Parrelli
parent 2c554a3a20
commit cf59249d3d
8 changed files with 64 additions and 58 deletions

View File

@@ -211,6 +211,7 @@ class V2ConversationItemShapeTest {
override val searchQuery: String? = null
override val glideRequests: GlideRequests = mockk()
override val isParentInScroll: Boolean = false
override fun getChatColorsData(): ChatColorsDrawable.ChatColorsData = ChatColorsDrawable.ChatColorsData(null, null)
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit

View File

@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.conversation.colors.Colorizer
import org.thoughtcrime.securesms.conversation.colors.RecyclerViewColorizer
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.v2.ConversationAdapterV2
import org.thoughtcrime.securesms.conversation.v2.items.ChatColorsDrawable
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
@@ -65,7 +66,8 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
clickListener = ClickListener(),
hasWallpaper = springboardViewModel.hasWallpaper.value,
colorizer = Colorizer(),
startExpirationTimeout = {}
startExpirationTimeout = {},
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) }
)
if (springboardViewModel.hasWallpaper.value) {

View File

@@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.conversation.v2.data.IncomingTextOnly
import org.thoughtcrime.securesms.conversation.v2.data.OutgoingMedia
import org.thoughtcrime.securesms.conversation.v2.data.OutgoingTextOnly
import org.thoughtcrime.securesms.conversation.v2.data.ThreadHeader
import org.thoughtcrime.securesms.conversation.v2.items.ChatColorsDrawable
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationContext
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemMediaViewHolder
import org.thoughtcrime.securesms.conversation.v2.items.V2ConversationItemTextOnlyViewHolder
@@ -66,7 +67,8 @@ class ConversationAdapterV2(
override val clickListener: ItemClickListener,
private var hasWallpaper: Boolean,
private val colorizer: Colorizer,
private val startExpirationTimeout: (MessageRecord) -> Unit
private val startExpirationTimeout: (MessageRecord) -> Unit,
private val chatColorsDataProvider: () -> ChatColorsDrawable.ChatColorsData
) : PagingMappingAdapter<ConversationElementKey>(), ConversationAdapterBridge, V2ConversationContext {
companion object {
@@ -183,6 +185,10 @@ class ConversationAdapterV2(
override fun getColorizer(): Colorizer = colorizer
override fun getChatColorsData(): ChatColorsDrawable.ChatColorsData {
return chatColorsDataProvider()
}
override fun getNextMessage(adapterPosition: Int): MessageRecord? {
return getConversationMessage(adapterPosition - 1)?.messageRecord
}

View File

@@ -400,7 +400,8 @@ class ConversationFragment :
repository = ConversationRepository(localContext = requireContext(), isInBubble = args.conversationScreenType == ConversationScreenType.BUBBLE),
recipientRepository = conversationRecipientRepository,
messageRequestRepository = messageRequestRepository,
scheduledMessagesRepository = ScheduledMessagesRepository()
scheduledMessagesRepository = ScheduledMessagesRepository(),
initialChatColors = args.chatColors
)
}
@@ -590,7 +591,11 @@ class ConversationFragment :
inputPanel.setMediaListener(InputPanelMediaListener())
ChatColorsDrawable.attach(binding.conversationItemRecycler)
binding.conversationItemRecycler.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
viewModel.onChatBoundsChanged(Rect(left, top, right, bottom))
}
binding.conversationItemRecycler.addItemDecoration(ChatColorsDrawable.ChatColorsItemDecoration)
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
@@ -1369,7 +1374,6 @@ class ConversationFragment :
colorFilter = PorterDuffColorFilter(chatColors.asSingleColor(), PorterDuff.Mode.MULTIPLY)
invalidateSelf()
}
ChatColorsDrawable.setGlobalChatColors(binding.conversationItemRecycler, chatColors)
}
private fun presentScrollButtons(scrollButtonState: ConversationScrollButtonState) {
@@ -1531,7 +1535,8 @@ class ConversationFragment :
clickListener = ConversationItemClickListener(),
hasWallpaper = args.wallpaper != null,
colorizer = colorizer,
startExpirationTimeout = viewModel::startExpirationTimeout
startExpirationTimeout = viewModel::startExpirationTimeout,
chatColorsDataProvider = viewModel::chatColorsSnapshot
)
typingIndicatorAdapter = ConversationTypingIndicatorAdapter(GlideApp.with(this))

View File

@@ -6,11 +6,13 @@
package org.thoughtcrime.securesms.conversation.v2
import android.content.Context
import android.graphics.Rect
import android.net.Uri
import android.os.Build
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.lifecycle.ViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.BackpressureStrategy
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
@@ -33,8 +35,10 @@ import org.thoughtcrime.securesms.components.reminder.Reminder
import org.thoughtcrime.securesms.contactshare.Contact
import org.thoughtcrime.securesms.conversation.ConversationMessage
import org.thoughtcrime.securesms.conversation.ScheduledMessagesRepository
import org.thoughtcrime.securesms.conversation.colors.ChatColors
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart
import org.thoughtcrime.securesms.conversation.v2.data.ConversationElementKey
import org.thoughtcrime.securesms.conversation.v2.items.ChatColorsDrawable
import org.thoughtcrime.securesms.database.DatabaseObserver
import org.thoughtcrime.securesms.database.MessageTable
import org.thoughtcrime.securesms.database.model.IdentityRecord
@@ -79,6 +83,7 @@ import kotlin.time.Duration
class ConversationViewModel(
val threadId: Long,
private val requestedStartingPosition: Int,
initialChatColors: ChatColors,
private val repository: ConversationRepository,
recipientRepository: ConversationRecipientRepository,
messageRequestRepository: MessageRequestRepository,
@@ -110,6 +115,10 @@ class ConversationViewModel(
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
private val chatBounds: BehaviorSubject<Rect> = BehaviorSubject.create()
private val chatColors: RxStore<ChatColorsDrawable.ChatColorsData> = RxStore(ChatColorsDrawable.ChatColorsData(initialChatColors, null))
val chatColorsSnapshot: ChatColorsDrawable.ChatColorsData get() = chatColors.state
@Volatile
var recipientSnapshot: Recipient? = null
private set
@@ -153,6 +162,19 @@ class ConversationViewModel(
recipientSnapshot = it
}
val chatColorsDataObservable: Observable<ChatColorsDrawable.ChatColorsData> = Observable.combineLatest(
recipient.map { it.chatColors }.distinctUntilChanged(),
chatBounds.distinctUntilChanged()
) { chatColors, bounds ->
val chatMask = chatColors.chatBubbleMask
chatMask.bounds = bounds
ChatColorsDrawable.ChatColorsData(chatColors, chatMask)
}
disposables += chatColors.update(chatColorsDataObservable.toFlowable(BackpressureStrategy.LATEST)) { c, _ -> c }
disposables += repository.getConversationThreadState(threadId, requestedStartingPosition)
.subscribeBy(onSuccess = {
pagingController.set(it.items.controller)
@@ -251,6 +273,10 @@ class ConversationViewModel(
})
}
fun onChatBoundsChanged(bounds: Rect) {
chatBounds.onNext(bounds)
}
fun setSearchQuery(query: String?) {
_searchQuery.onNext(query ?: "")
}

View File

@@ -11,7 +11,6 @@ import android.graphics.Outline
import android.graphics.Path
import android.graphics.PixelFormat
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.view.ViewGroup
@@ -29,52 +28,17 @@ import org.thoughtcrime.securesms.util.Projection.Corners
* Drawable that renders the given chat colors at a specified coordinate offset.
* This is meant to be used in conjunction with [ChatColorsItemDecoration]
*/
class ChatColorsDrawable : Drawable() {
class ChatColorsDrawable(
private val dataProvider: () -> ChatColorsData
) : Drawable() {
companion object {
private var globalChatColors: ChatColors? = null
private var globalMask: Drawable? = null
private var latestBounds: Rect? = null
/**
* Binds the ChatColorsDrawable static cache to the lifecycle of the given recycler-view
*/
fun attach(recyclerView: RecyclerView) {
recyclerView.addOnLayoutChangeListener { _, left, top, right, bottom, _, _, _, _ ->
applyBounds(Rect(left, top, right, bottom))
}
recyclerView.addItemDecoration(ChatColorsItemDecoration)
}
fun setGlobalChatColors(recyclerView: RecyclerView, chatColors: ChatColors) {
if (globalChatColors == chatColors) {
return
}
globalChatColors = chatColors
globalMask = if (chatColors.isGradient()) {
chatColors.chatBubbleMask
} else {
null
}
recyclerView.invalidateItemDecorations()
}
fun clearGlobalChatColors(recyclerView: RecyclerView) {
globalChatColors = null
globalMask = null
recyclerView.invalidateItemDecorations()
}
private fun applyBounds(bounds: Rect) {
latestBounds = bounds
globalMask?.bounds = bounds
}
}
/**
* Object allowing you to inject global color / masking.
*/
data class ChatColorsData(
var chatColors: ChatColors?,
var mask: Drawable?
)
/**
* Translation coordinates so that the mask is drawn at the right location
@@ -200,11 +164,11 @@ class ChatColorsDrawable : Drawable() {
invalidateSelf()
}
private fun getChatColors(): ChatColors? = localChatColors ?: globalChatColors
private fun getChatColors(): ChatColors? = localChatColors ?: dataProvider().chatColors
private fun getMask(): Drawable? = localMask ?: globalMask
private fun getMask(): Drawable? = localMask ?: dataProvider().mask
private object ChatColorsItemDecoration : RecyclerView.ItemDecoration() {
object ChatColorsItemDecoration : RecyclerView.ItemDecoration() {
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
parent.children.map { parent.getChildViewHolder(it) }.filterIsInstance<ChatColorsDrawableInvalidator>().forEach { element ->
element.invalidateChatColorsDrawable(parent)

View File

@@ -25,6 +25,8 @@ interface V2ConversationContext {
val searchQuery: String?
val isParentInScroll: Boolean
fun getChatColorsData(): ChatColorsDrawable.ChatColorsData
fun onStartExpirationTimeout(messageRecord: MessageRecord)
fun hasWallpaper(): Boolean

View File

@@ -103,9 +103,9 @@ open class V2ConversationItemTextOnlyViewHolder<Model : MappingModel<Model>>(
private var reactionMeasureListener: ReactionMeasureListener = ReactionMeasureListener()
private var formattedDate: FormattedDate? = null
private val bodyBubbleDrawable = ChatColorsDrawable()
private val footerDrawable = ChatColorsDrawable()
private val senderDrawable = ChatColorsDrawable()
private val bodyBubbleDrawable = ChatColorsDrawable(conversationContext::getChatColorsData)
private val footerDrawable = ChatColorsDrawable(conversationContext::getChatColorsData)
private val senderDrawable = ChatColorsDrawable(conversationContext::getChatColorsData)
private val bodyBubbleLayoutTransition = BodyBubbleLayoutTransition()
protected lateinit var shape: V2ConversationItemShape.MessageShape