mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Rewrite chat colors delegation.
This commit is contained in:
committed by
Greyson Parrelli
parent
2c554a3a20
commit
cf59249d3d
@@ -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
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 ?: "")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -25,6 +25,8 @@ interface V2ConversationContext {
|
||||
val searchQuery: String?
|
||||
val isParentInScroll: Boolean
|
||||
|
||||
fun getChatColorsData(): ChatColorsDrawable.ChatColorsData
|
||||
|
||||
fun onStartExpirationTimeout(messageRecord: MessageRecord)
|
||||
|
||||
fun hasWallpaper(): Boolean
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user