From cf59249d3d0d47e2e2a2588a7ebef1dda754a0df Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Tue, 16 Jan 2024 15:19:11 -0400 Subject: [PATCH] Rewrite chat colors delegation. --- .../v2/items/V2ConversationItemShapeTest.kt | 1 + .../test/InternalConversationTestFragment.kt | 4 +- .../conversation/v2/ConversationAdapterV2.kt | 8 ++- .../conversation/v2/ConversationFragment.kt | 13 ++-- .../conversation/v2/ConversationViewModel.kt | 26 ++++++++ .../v2/items/ChatColorsDrawable.kt | 62 ++++--------------- .../v2/items/V2ConversationContext.kt | 2 + .../V2ConversationItemTextOnlyViewHolder.kt | 6 +- 8 files changed, 64 insertions(+), 58 deletions(-) diff --git a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt index 5befcad433..d24fb9df35 100644 --- a/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt +++ b/app/src/androidTest/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemShapeTest.kt @@ -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 diff --git a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt index df4f057714..b974e4b62e 100644 --- a/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt +++ b/app/src/debug/java/org/thoughtcrime/securesms/components/settings/app/internal/conversation/test/InternalConversationTestFragment.kt @@ -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) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt index 5ec574ca43..07ac9df4fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapterV2.kt @@ -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(), 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 } 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 a7acf0f882..616dc94fb2 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 @@ -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)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 008a5c2304..3fb1ee2173 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -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 = BehaviorSubject.create() + private val chatColors: RxStore = 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 = 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 ?: "") } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable.kt index a8c1c2227a..61ab2410f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/ChatColorsDrawable.kt @@ -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().forEach { element -> element.invalidateChatColorsDrawable(parent) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext.kt index 9de9b631cd..405201bb9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationContext.kt @@ -25,6 +25,8 @@ interface V2ConversationContext { val searchQuery: String? val isParentInScroll: Boolean + fun getChatColorsData(): ChatColorsDrawable.ChatColorsData + fun onStartExpirationTimeout(messageRecord: MessageRecord) fun hasWallpaper(): Boolean diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt index 086d13bd21..d45c80de5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/items/V2ConversationItemTextOnlyViewHolder.kt @@ -103,9 +103,9 @@ open class V2ConversationItemTextOnlyViewHolder>( 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