From ded29619cd52d71f726cf19f3862acfa1c6d9a1d Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Tue, 25 Jul 2023 16:53:45 -0400 Subject: [PATCH] Add payload support to CFv2. --- .../conversation/ConversationAdapter.java | 4 - .../conversation/ConversationAdapterBridge.kt | 6 ++ .../conversation/ConversationUpdateTick.kt | 6 ++ .../quotes/MessageQuotesBottomSheet.kt | 3 +- .../ui/edit/EditMessageHistoryDialog.kt | 3 +- .../conversation/v2/ConversationAdapterV2.kt | 84 +++++++++++++++++-- .../conversation/v2/ConversationFragment.kt | 30 ++++++- 7 files changed, 122 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java index 7e92092d99..83821a5a92 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapter.java @@ -96,10 +96,6 @@ public class ConversationAdapter public static final int MESSAGE_TYPE_FOOTER = 6; private static final int MESSAGE_TYPE_PLACEHOLDER = 7; - private static final int PAYLOAD_TIMESTAMP = 0; - public static final int PAYLOAD_NAME_COLORS = 1; - public static final int PAYLOAD_SELECTED = 2; - private final ItemClickListener clickListener; private final Context context; private final LifecycleOwner lifecycleOwner; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapterBridge.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapterBridge.kt index 8533aad2c7..2b1768cdf0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapterBridge.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationAdapterBridge.kt @@ -12,6 +12,12 @@ import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart * shared decorators and other utils. */ interface ConversationAdapterBridge { + companion object { + const val PAYLOAD_TIMESTAMP = 0 + const val PAYLOAD_NAME_COLORS = 1 + const val PAYLOAD_SELECTED = 2 + } + fun hasNoConversationMessages(): Boolean fun getConversationMessage(position: Int): ConversationMessage? fun consumePulseRequest(): PulseRequest? diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateTick.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateTick.kt index 8b176eeed0..092f8ec7f8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateTick.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationUpdateTick.kt @@ -18,6 +18,12 @@ class ConversationUpdateTick( private val handler = Handler(Looper.getMainLooper()) private var isResumed = false + constructor(onTickListener: () -> Unit) : this(object : OnTickListener { + override fun onTick() { + onTickListener() + } + }) + override fun onResume(owner: LifecycleOwner) { isResumed = true diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt index 9ea98f6c8d..d25682b389 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/quotes/MessageQuotesBottomSheet.kt @@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager import org.thoughtcrime.securesms.conversation.ConversationAdapter +import org.thoughtcrime.securesms.conversation.ConversationAdapterBridge import org.thoughtcrime.securesms.conversation.ConversationBottomSheetCallback import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode import org.thoughtcrime.securesms.conversation.colors.Colorizer @@ -117,7 +118,7 @@ class MessageQuotesBottomSheet : FixedRoundedCornerBottomSheetDialogFragment() { disposables += viewModel.getNameColorsMap().subscribe { map -> colorizer.onNameColorsChanged(map) - messageAdapter.notifyItemRangeChanged(0, messageAdapter.itemCount, ConversationAdapter.PAYLOAD_NAME_COLORS) + messageAdapter.notifyItemRangeChanged(0, messageAdapter.itemCount, ConversationAdapterBridge.PAYLOAD_NAME_COLORS) } initializeGiphyMp4(view.findViewById(R.id.video_container) as ViewGroup, list) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryDialog.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryDialog.kt index a7313d2fa6..1de30f1a54 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryDialog.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ui/edit/EditMessageHistoryDialog.kt @@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.FixedRoundedCornerBottomSheetDialogFragment import org.thoughtcrime.securesms.components.ViewBinderDelegate import org.thoughtcrime.securesms.conversation.ConversationAdapter +import org.thoughtcrime.securesms.conversation.ConversationAdapterBridge import org.thoughtcrime.securesms.conversation.ConversationBottomSheetCallback import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode import org.thoughtcrime.securesms.conversation.ConversationMessage @@ -113,7 +114,7 @@ class EditMessageHistoryDialog : FixedRoundedCornerBottomSheetDialogFragment() { disposables += viewModel.getNameColorsMap().subscribe { map -> colorizer.onNameColorsChanged(map) - messageAdapter.notifyItemRangeChanged(0, messageAdapter.itemCount, ConversationAdapter.PAYLOAD_NAME_COLORS) + messageAdapter.notifyItemRangeChanged(0, messageAdapter.itemCount, ConversationAdapterBridge.PAYLOAD_NAME_COLORS) } initializeGiphyMp4() 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 cf3b137feb..2dc9f96d3f 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 @@ -198,7 +198,7 @@ class ConversationAdapterV2( fun playInlineContent(conversationMessage: ConversationMessage?) { if (this.inlineContent !== conversationMessage) { this.inlineContent = conversationMessage - notifyDataSetChanged() + notifyItemRangeChanged(0, itemCount) } } @@ -234,8 +234,15 @@ class ConversationAdapterV2( return request } - fun onHasWallpaperChanged(hasChanged: Boolean) { - // todo [cody] implement + fun onHasWallpaperChanged(hasWallpaper: Boolean): Boolean { + return if (this.hasWallpaper != hasWallpaper) { + Log.d(TAG, "Resetting adapter due to wallpaper change.") + this.hasWallpaper = hasWallpaper + notifyItemRangeChanged(0, itemCount) + true + } else { + false + } } fun onMessageRequestStateChanged(isMessageRequestAccepted: Boolean) { @@ -249,6 +256,7 @@ class ConversationAdapterV2( fun clearSelection() { _selected.clear() + updateSelected() } fun toggleSelection(multiselectPart: MultiselectPart) { @@ -257,11 +265,34 @@ class ConversationAdapterV2( } else { _selected.add(multiselectPart) } + updateSelected() + } + + fun removeFromSelection(expired: Set) { + _selected.removeAll(expired) + updateSelected() + } + + fun updateTimestamps() { + notifyItemRangeChanged(0, itemCount, ConversationAdapterBridge.PAYLOAD_TIMESTAMP) + } + + fun updateNameColors() { + notifyItemRangeChanged(0, itemCount, ConversationAdapterBridge.PAYLOAD_NAME_COLORS) + } + + private fun updateSelected() { + notifyItemRangeChanged(0, itemCount, ConversationAdapterBridge.PAYLOAD_SELECTED) } private inner class ConversationUpdateViewHolder(itemView: View) : ConversationViewHolder(itemView) { override fun bind(model: ConversationUpdate) { bindable.setEventListener(clickListener) + + if (bindPayloadsIfAvailable()) { + return + } + bindable.bind( lifecycleOwner, model.conversationMessage, @@ -274,7 +305,7 @@ class ConversationAdapterV2( searchQuery, false, hasWallpaper && displayMode.displayWallpaper(), - true, // isMessageRequestAccepted, + isMessageRequestAccepted, model.conversationMessage == inlineContent, colorizer, displayMode @@ -285,6 +316,11 @@ class ConversationAdapterV2( private inner class OutgoingTextOnlyViewHolder(itemView: View) : ConversationViewHolder(itemView) { override fun bind(model: OutgoingTextOnly) { bindable.setEventListener(clickListener) + + if (bindPayloadsIfAvailable()) { + return + } + bindable.bind( lifecycleOwner, model.conversationMessage, @@ -297,7 +333,7 @@ class ConversationAdapterV2( searchQuery, false, hasWallpaper && displayMode.displayWallpaper(), - true, // isMessageRequestAccepted, + isMessageRequestAccepted, model.conversationMessage == inlineContent, colorizer, displayMode @@ -308,6 +344,11 @@ class ConversationAdapterV2( private inner class OutgoingMediaViewHolder(itemView: View) : ConversationViewHolder(itemView) { override fun bind(model: OutgoingMedia) { bindable.setEventListener(clickListener) + + if (bindPayloadsIfAvailable()) { + return + } + bindable.bind( lifecycleOwner, model.conversationMessage, @@ -331,6 +372,11 @@ class ConversationAdapterV2( private inner class IncomingTextOnlyViewHolder(itemView: View) : ConversationViewHolder(itemView) { override fun bind(model: IncomingTextOnly) { bindable.setEventListener(clickListener) + + if (bindPayloadsIfAvailable()) { + return + } + bindable.bind( lifecycleOwner, model.conversationMessage, @@ -343,7 +389,7 @@ class ConversationAdapterV2( searchQuery, false, hasWallpaper && displayMode.displayWallpaper(), - true, // isMessageRequestAccepted, + isMessageRequestAccepted, model.conversationMessage == inlineContent, colorizer, displayMode @@ -354,6 +400,11 @@ class ConversationAdapterV2( private inner class IncomingMediaViewHolder(itemView: View) : ConversationViewHolder(itemView) { override fun bind(model: IncomingMedia) { bindable.setEventListener(clickListener) + + if (bindPayloadsIfAvailable()) { + return + } + bindable.bind( lifecycleOwner, model.conversationMessage, @@ -406,6 +457,27 @@ class ConversationAdapterV2( } } + fun bindPayloadsIfAvailable(): Boolean { + var payloadApplied = false + + if (payload.contains(ConversationAdapterBridge.PAYLOAD_TIMESTAMP)) { + bindable.updateTimestamps() + payloadApplied = true + } + + if (payload.contains(ConversationAdapterBridge.PAYLOAD_NAME_COLORS)) { + bindable.updateContactNameColor() + payloadApplied = true + } + + if (payload.contains(ConversationAdapterBridge.PAYLOAD_SELECTED)) { + bindable.updateSelectedState() + payloadApplied = true + } + + return payloadApplied + } + override fun showProjectionArea() { bindable.showProjectionArea() } 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 12729bebef..73163bf7dc 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 @@ -147,6 +147,7 @@ import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay.OnActionSelectedListener import org.thoughtcrime.securesms.conversation.ConversationReactionOverlay.OnHideListener import org.thoughtcrime.securesms.conversation.ConversationSearchViewModel +import org.thoughtcrime.securesms.conversation.ConversationUpdateTick import org.thoughtcrime.securesms.conversation.MarkReadHelper import org.thoughtcrime.securesms.conversation.MenuState import org.thoughtcrime.securesms.conversation.MessageSendType @@ -820,7 +821,7 @@ class ConversationFragment : .observeOn(AndroidSchedulers.mainThread()) .subscribeBy(onNext = { colorizer.onNameColorsChanged(it) - adapter.notifyItemRangeChanged(0, adapter.itemCount) + adapter.updateNameColors() }) val disabledInputListener = DisabledInputListener() @@ -966,6 +967,9 @@ class ConversationFragment : } getVoiceNoteMediaController().voiceNotePlaybackState.observe(viewLifecycleOwner, inputPanel.playbackStateObserver) + + val conversationUpdateTick = ConversationUpdateTick { adapter.updateTimestamps() } + viewLifecycleOwner.lifecycle.addObserver(conversationUpdateTick) } private fun initializeInlineSearch() { @@ -1224,8 +1228,11 @@ class ConversationFragment : binding.conversationDisabledInput.setWallpaperEnabled(wallpaperEnabled) inputPanel.setWallpaperEnabled(wallpaperEnabled) - adapter.onHasWallpaperChanged(wallpaperEnabled) + val stateChanged = adapter.onHasWallpaperChanged(wallpaperEnabled) conversationItemDecorations.hasWallpaper = wallpaperEnabled + if (stateChanged) { + binding.conversationItemRecycler.invalidateItemDecorations() + } val navColor = if (wallpaperEnabled) { R.color.conversation_navigation_wallpaper @@ -2295,6 +2302,25 @@ class ConversationFragment : layoutManager.scrollToPositionWithOffset(0, 0) } } + + override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) { + if (actionMode == null) { + return + } + + val expired: Set = adapter + .selectedItems + .filter { it.isExpired() } + .toSet() + + adapter.removeFromSelection(expired) + + if (adapter.selectedItems.isEmpty()) { + actionMode?.finish() + } else { + actionMode?.setTitle(calculateSelectedItemCount()) + } + } } //endregion Scroll Handling