From f3b629bc06f8390096f700d75f2127d4404cf4fd Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Mon, 12 Jun 2023 10:23:57 -0300 Subject: [PATCH] Add reaction support to CFV2. --- .../conversation/v2/ConversationFragment.kt | 35 +++++++++++++++++-- .../conversation/v2/ConversationRepository.kt | 22 ++++++++++++ .../conversation/v2/ConversationViewModel.kt | 28 +++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) 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 a791db90a7..170b21d143 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 @@ -208,6 +208,7 @@ import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.profiles.spoofing.ReviewCardDialogFragment import org.thoughtcrime.securesms.ratelimit.RecaptchaProofBottomSheetFragment import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment +import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiBottomSheetDialogFragment import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientExporter import org.thoughtcrime.securesms.recipients.RecipientFormattingException @@ -258,7 +259,10 @@ import java.util.concurrent.ExecutionException /** * A single unified fragment for Conversations. */ -class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) { +class ConversationFragment : + LoggingFragment(R.layout.v2_conversation_fragment), + ReactWithAnyEmojiBottomSheetDialogFragment.Callback, + ReactionsBottomSheetDialogFragment.Callback { companion object { private val TAG = Log.tag(ConversationFragment::class.java) @@ -442,6 +446,18 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) } } + override fun onReactWithAnyEmojiDialogDismissed() { + reactionDelegate.hide() + } + + override fun onReactWithAnyEmojiSelected(emoji: String) { + reactionDelegate.hide() + } + + override fun onReactionsDialogDismissed() { + clearFocusedItem() + } + private fun observeConversationThread() { var firstRender = true disposables += viewModel @@ -1580,13 +1596,13 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) context ?: return val reactionsTag = "REACTIONS" if (parentFragmentManager.findFragmentByTag(reactionsTag) == null) { - ReactionsBottomSheetDialogFragment.create(messageId, isMms).show(parentFragmentManager, reactionsTag) + ReactionsBottomSheetDialogFragment.create(messageId, isMms).show(childFragmentManager, reactionsTag) } } override fun onGroupMemberClicked(recipientId: RecipientId, groupId: GroupId) { context ?: return - RecipientBottomSheetDialogFragment.create(recipientId, groupId).show(parentFragmentManager, "BOTTOM") + RecipientBottomSheetDialogFragment.create(recipientId, groupId).show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) } override fun onMessageWithErrorClicked(messageRecord: MessageRecord) { @@ -2150,10 +2166,23 @@ class ConversationFragment : LoggingFragment(R.layout.v2_conversation_fragment) private inner class OnReactionsSelectedListener : ConversationReactionOverlay.OnReactionSelectedListener { override fun onReactionSelected(messageRecord: MessageRecord, emoji: String?) { reactionDelegate.hide() + + if (emoji != null) { + disposables += viewModel.updateReaction(messageRecord, emoji).subscribe() + } } override fun onCustomReactionSelected(messageRecord: MessageRecord, hasAddedCustomEmoji: Boolean) { reactionDelegate.hide() + disposables += viewModel.updateCustomReaction(messageRecord, hasAddedCustomEmoji) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy( + onSuccess = { + ReactWithAnyEmojiBottomSheetDialogFragment + .createForMessageRecord(messageRecord, -1) + .show(childFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG) + } + ) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt index b904f5fb4f..d78f6314ca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationRepository.kt @@ -63,8 +63,10 @@ import org.thoughtcrime.securesms.database.model.GroupRecord import org.thoughtcrime.securesms.database.model.IdentityRecord import org.thoughtcrime.securesms.database.model.Mention import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.Quote +import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob @@ -159,6 +161,26 @@ class ConversationRepository( .subscribeOn(Schedulers.io()) } + fun sendReactionRemoval(messageRecord: MessageRecord, oldRecord: ReactionRecord): Completable { + return Completable.fromAction { + MessageSender.sendReactionRemoval( + applicationContext, + MessageId(messageRecord.id), + oldRecord + ) + }.subscribeOn(Schedulers.io()) + } + + fun sendNewReaction(messageRecord: MessageRecord, emoji: String): Completable { + return Completable.fromAction { + MessageSender.sendNewReaction( + applicationContext, + MessageId(messageRecord.id), + emoji + ) + }.subscribeOn(Schedulers.io()) + } + fun sendMessage( threadId: Long, threadRecipient: Recipient?, 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 a96ce5e045..7a9be49d5f 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 @@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.Quote +import org.thoughtcrime.securesms.database.model.ReactionRecord import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.jobs.RetrieveProfileJob @@ -248,6 +249,33 @@ class ConversationViewModel( } } + fun updateReaction(messageRecord: MessageRecord, emoji: String): Completable { + val oldRecord = messageRecord.oldReactionRecord() + + return if (oldRecord != null && oldRecord.emoji == emoji) { + repository.sendReactionRemoval(messageRecord, oldRecord) + } else { + repository.sendNewReaction(messageRecord, emoji) + } + } + + /** + * @return Maybe which only emits if the "React with any" sheet should be displayed. + */ + fun updateCustomReaction(messageRecord: MessageRecord, hasAddedCustomEmoji: Boolean): Maybe { + val oldRecord = messageRecord.oldReactionRecord() + + return if (oldRecord != null && hasAddedCustomEmoji) { + repository.sendReactionRemoval(messageRecord, oldRecord).toMaybe() + } else { + Maybe.just(Unit) + } + } + + private fun MessageRecord.oldReactionRecord(): ReactionRecord? { + return reactions.firstOrNull { it.author == Recipient.self().id } + } + fun sendMessage( metricId: String?, body: String,