From 276d71d365027a2efccc0b15d1d82036e7f30995 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Fri, 12 Jun 2026 11:04:18 -0300 Subject: [PATCH] Decouple add message dialog from old view model. --- .../keyboard/emoji/EmojiKeyboardEvent.kt | 18 +++ .../emoji/EmojiKeyboardEventViewModel.kt | 28 +++++ .../securesms/mediasend/v2/HudCommand.kt | 7 -- .../mediasend/v2/MediaSelectionActivity.kt | 13 ++- .../mediasend/v2/MediaSelectionViewModel.kt | 23 ---- .../v2/review/AddMessageDialogFragment.kt | 107 ++++++++++-------- .../v2/review/AddMessageViewModel.kt | 42 +++++++ .../v2/review/MediaReviewFragment.kt | 29 ++++- 8 files changed, 186 insertions(+), 81 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/EmojiKeyboardEvent.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/EmojiKeyboardEventViewModel.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageViewModel.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/EmojiKeyboardEvent.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/EmojiKeyboardEvent.kt new file mode 100644 index 0000000000..0fee593bd7 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/EmojiKeyboardEvent.kt @@ -0,0 +1,18 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.keyboard.emoji + +import android.view.KeyEvent + +/** + * Mapping of [EmojiKeyboardCallback] methods into a sealed event class + */ +sealed interface EmojiKeyboardEvent { + object OpenEmojiSearch : EmojiKeyboardEvent + object CloseEmojiSearch : EmojiKeyboardEvent + data class EmojiInsert(val emoji: String?) : EmojiKeyboardEvent + data class EmojiKeyEvent(val keyEvent: KeyEvent?) : EmojiKeyboardEvent +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/EmojiKeyboardEventViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/EmojiKeyboardEventViewModel.kt new file mode 100644 index 0000000000..c7059dae80 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/keyboard/emoji/EmojiKeyboardEventViewModel.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.keyboard.emoji + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch + +/** + * Glue ViewModel that allows a component to dispatch emoji events to subcomponents. + */ +class EmojiKeyboardEventViewModel : ViewModel() { + + private val eventChannel = Channel(Channel.BUFFERED) + val events: Flow = eventChannel.receiveAsFlow() + + fun onEvent(event: EmojiKeyboardEvent) { + viewModelScope.launch { + eventChannel.send(event) + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/HudCommand.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/HudCommand.kt index 4ef01ad242..7053b1cbc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/HudCommand.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/HudCommand.kt @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.mediasend.v2 -import android.view.KeyEvent - sealed class HudCommand { object StartDraw : HudCommand() object StartCropAndRotate : HudCommand() @@ -11,9 +9,4 @@ sealed class HudCommand { object GoToCapture : HudCommand() object ResumeEntryTransition : HudCommand() - - object OpenEmojiSearch : HudCommand() - object CloseEmojiSearch : HudCommand() - data class EmojiInsert(val emoji: String?) : HudCommand() - data class EmojiKeyEvent(val keyEvent: KeyEvent?) : HudCommand() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt index e488631c79..5e60bc6202 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt @@ -37,6 +37,8 @@ import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.emoji.EmojiEventListener import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.MessageSendType +import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardEvent +import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardEventViewModel import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil @@ -52,6 +54,7 @@ import org.thoughtcrime.securesms.util.FullscreenHelper import org.thoughtcrime.securesms.util.WindowUtil import org.thoughtcrime.securesms.util.navigation.safeNavigate import org.thoughtcrime.securesms.util.visible +import kotlin.getValue import org.signal.core.ui.R as CoreUiR class MediaSelectionActivity : @@ -76,6 +79,8 @@ class MediaSelectionActivity : } ) + private val addMessageCommandViewModel: EmojiKeyboardEventViewModel by viewModels() + private val destination: MediaSelectionDestination get() = MediaSelectionDestination.fromBundle(requireNotNull(intent.getBundleExtra(DESTINATION))) @@ -350,19 +355,19 @@ class MediaSelectionActivity : private fun isCameraFirst(): Boolean = intent.getIntExtra(START_ACTION, -1) == R.id.action_directly_to_mediaCaptureFragment override fun openEmojiSearch() { - viewModel.sendCommand(HudCommand.OpenEmojiSearch) + addMessageCommandViewModel.onEvent(EmojiKeyboardEvent.OpenEmojiSearch) } override fun onEmojiSelected(emoji: String?) { - viewModel.sendCommand(HudCommand.EmojiInsert(emoji)) + addMessageCommandViewModel.onEvent(EmojiKeyboardEvent.EmojiInsert(emoji)) } override fun onKeyEvent(keyEvent: KeyEvent?) { - viewModel.sendCommand(HudCommand.EmojiKeyEvent(keyEvent)) + addMessageCommandViewModel.onEvent(EmojiKeyboardEvent.EmojiKeyEvent(keyEvent)) } override fun closeEmojiSearch() { - viewModel.sendCommand(HudCommand.CloseEmojiSearch) + addMessageCommandViewModel.onEvent(EmojiKeyboardEvent.CloseEmojiSearch) } private inner class OnBackPressed : OnBackPressedCallback(true) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt index 504d7c52d3..82f46785f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionViewModel.kt @@ -8,8 +8,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.google.common.io.ByteStreams -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers -import io.reactivex.rxjava3.core.Flowable import io.reactivex.rxjava3.core.Maybe import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single @@ -17,13 +15,11 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.kotlin.plusAssign import io.reactivex.rxjava3.kotlin.subscribeBy -import io.reactivex.rxjava3.processors.BehaviorProcessor import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.BehaviorSubject import io.reactivex.rxjava3.subjects.PublishSubject import io.reactivex.rxjava3.subjects.Subject import org.signal.core.models.media.Media -import org.signal.core.util.BreakIteratorCompat import org.signal.core.util.Util import org.signal.core.util.getParcelableArrayListCompat import org.signal.core.util.getParcelableCompat @@ -33,7 +29,6 @@ import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.MessageSendType import org.thoughtcrime.securesms.conversation.MessageStyler import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult -import org.thoughtcrime.securesms.mediasend.v2.review.AddMessageCharacterCount import org.thoughtcrime.securesms.mediasend.v2.videos.VideoTrimData import org.thoughtcrime.securesms.mms.MediaConstraints import org.thoughtcrime.securesms.mms.SentMediaQuality @@ -74,8 +69,6 @@ class MediaSelectionViewModel( ) ) - private val addAMessageUpdatePublisher = BehaviorProcessor.create() - val isContactSelectionRequired = destination == MediaSelectionDestination.ChooseAfterMediaSelection val state: LiveData = store.stateLiveData @@ -87,22 +80,6 @@ class MediaSelectionViewModel( private val disposables = CompositeDisposable() - fun watchAddAMessageCount(): Flowable { - return addAMessageUpdatePublisher - .onBackpressureLatest() - .map { - val iterator = BreakIteratorCompat.getInstance() - iterator.setText(it) - AddMessageCharacterCount(iterator.countBreaks()) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - } - - fun updateAddAMessageCount(input: CharSequence?) { - addAMessageUpdatePublisher.onNext(input ?: "") - } - private val isMeteredDisposable: Disposable = repository.isMetered.subscribe { metered -> store.update { it.copy( diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageDialogFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageDialogFragment.kt index bac5bb4b54..9d497c4427 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageDialogFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageDialogFragment.kt @@ -11,14 +11,21 @@ import android.view.ViewGroup import androidx.core.view.ViewCompat import androidx.core.widget.addTextChangedListener import androidx.fragment.app.FragmentManager -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.setFragmentResult +import androidx.lifecycle.Lifecycle import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.kotlin.plusAssign +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.signal.core.ui.view.Stub import org.signal.core.util.ByteLimitInputFilter import org.signal.core.util.EditTextUtil +import org.signal.core.util.getParcelableCompat import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.backup.v2.ui.warning.guardAgainstRecoveryKeyPaste import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout @@ -36,32 +43,29 @@ import org.thoughtcrime.securesms.conversation.ui.mentions.MentionsPickerViewMod import org.thoughtcrime.securesms.databinding.V2MediaAddMessageDialogFragmentBinding import org.thoughtcrime.securesms.keyboard.KeyboardPage import org.thoughtcrime.securesms.keyboard.KeyboardPagerViewModel -import org.thoughtcrime.securesms.mediasend.v2.HudCommand -import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionState -import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel +import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardEvent +import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardEventViewModel import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.stories.Stories -import org.thoughtcrime.securesms.util.MediaUtil import org.thoughtcrime.securesms.util.MessageUtil import org.thoughtcrime.securesms.util.ViewUtil +import org.thoughtcrime.securesms.util.viewModel import org.thoughtcrime.securesms.util.visible class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_add_message_dialog_fragment) { - private val viewModel: MediaSelectionViewModel by viewModels( - ownerProducer = { requireActivity() } - ) + private val viewModel: AddMessageViewModel by viewModel { + AddMessageViewModel(initialMessage = requireArguments().getCharSequence(ARG_INITIAL_TEXT, null)) + } - private val keyboardPagerViewModel: KeyboardPagerViewModel by viewModels( - ownerProducer = { requireActivity() } - ) + private val commandViewModel: EmojiKeyboardEventViewModel by activityViewModels() + + private val keyboardPagerViewModel: KeyboardPagerViewModel by activityViewModels() private lateinit var mentionsViewModel: MentionsPickerViewModel - private val inlineQueryViewModel: InlineQueryViewModel by viewModels( - ownerProducer = { requireActivity() } - ) + private val inlineQueryViewModel: InlineQueryViewModel by activityViewModels() private val binding by ViewBinderDelegate(V2MediaAddMessageDialogFragmentBinding::bind, onBindingWillBeDestroyed = { binding -> binding.content.addAMessageInput.setInlineQueryChangedListener(null) @@ -86,6 +90,11 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + if (viewModel.isViewOnce) { + dismissAllowingStateLoss() + return + } + emojiDrawerStub = Stub(binding.content.emojiDrawerStub) if (Stories.isFeatureEnabled()) { @@ -97,7 +106,7 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a }) binding.content.addAMessageInput.setText(requireArguments().getCharSequence(ARG_INITIAL_TEXT)) - binding.content.addAMessageInput.addTextChangedListener { viewModel.setMessage(it) } + binding.content.addAMessageInput.addTextChangedListener { viewModel.message = it } binding.content.addAMessageInput.filters += ByteLimitInputFilter(MessageUtil.MAX_TOTAL_BODY_SIZE_BYTES) binding.content.addAMessageInput.guardAgainstRecoveryKeyPaste( host = this, @@ -116,43 +125,36 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a binding.hud.setOnClickListener { dismissAllowingStateLoss() } binding.content.viewOnceToggle.setOnClickListener { + viewModel.message = null + viewModel.isViewOnce = true + dismissAllowingStateLoss() - viewModel.incrementViewOnceState() } val confirm: View = view.findViewById(R.id.confirm_button) confirm.setOnClickListener { dismissAllowingStateLoss() } - disposables += viewModel.watchAddAMessageCount().subscribe { count -> - binding.content.addAMessageLimit.visible = count.shouldDisplayCount() - binding.content.addAMessageLimit.text = count.getRemaining().toString() + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) { + viewModel.watchAddAMessageCount().collect { count -> + binding.content.addAMessageLimit.visible = count.shouldDisplayCount() + binding.content.addAMessageLimit.text = count.getRemaining().toString() + } } - disposables.add( - viewModel.hudCommands.observeOn(AndroidSchedulers.mainThread()).subscribe { - when (it) { - HudCommand.OpenEmojiSearch -> openEmojiSearch() - HudCommand.CloseEmojiSearch -> closeEmojiSearch() - is HudCommand.EmojiKeyEvent -> onKeyEvent(it.keyEvent) - is HudCommand.EmojiInsert -> onEmojiSelected(it.emoji) - else -> Unit + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(state = Lifecycle.State.RESUMED) { + commandViewModel.events.collect { + when (it) { + EmojiKeyboardEvent.CloseEmojiSearch -> closeEmojiSearch() + is EmojiKeyboardEvent.EmojiInsert -> onEmojiSelected(it.emoji) + is EmojiKeyboardEvent.EmojiKeyEvent -> onKeyEvent(it.keyEvent) + EmojiKeyboardEvent.OpenEmojiSearch -> openEmojiSearch() + } } } - ) - - viewModel.state.observe(viewLifecycleOwner) { state -> - val newChild = if (state.viewOnceToggleState == MediaSelectionState.ViewOnceToggleState.ONCE) 1 else 0 - if (binding.content.viewOnceToggle.displayedChild != newChild) { - binding.content.viewOnceToggle.displayedChild = newChild - } - - if (state.viewOnceToggleState == MediaSelectionState.ViewOnceToggleState.ONCE) { - binding.content.addAMessageInput.text = null - dismiss() - } - binding.content.viewOnceToggle.visible = state.selectedMedia.size == 1 && !state.isStory && !MediaUtil.isDocumentType(state.focusedMedia?.contentType) } + binding.content.viewOnceToggle.visible = requireArguments().getBoolean(ARG_VIEW_ONCE_AVAILABLE, false) initializeMentions() } @@ -165,9 +167,14 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a override fun onDismiss(dialog: DialogInterface) { super.onDismiss(dialog) - if (isResumed) { - viewModel.setMessage(binding.content.addAMessageInput.text) - } + + setFragmentResult( + REQUEST_KEY, + Bundle().apply { + putCharSequence(RESULT_MESSAGE, viewModel.message) + putBoolean(RESULT_INCREMENT_VIEW_ONCE_STATE, viewModel.isViewOnce) + } + ) } override fun onKeyboardHidden() { @@ -213,10 +220,12 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a } inlineQueryViewModel.onQueryChange(inlineQuery) } + is InlineQuery.Emoji -> { inlineQueryViewModel.onQueryChange(inlineQuery) mentionsViewModel.onQueryChange(null) } + is NoQuery -> { mentionsViewModel.onQueryChange(null) inlineQueryViewModel.onQueryChange(inlineQuery) @@ -234,7 +243,7 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a .observeOn(AndroidSchedulers.mainThread()) .subscribe { r -> binding.content.addAMessageInput.replaceText(r) } - val recipientId: RecipientId = viewModel.destination.getRecipientSearchKey()?.recipientId ?: return + val recipientId: RecipientId = requireArguments().getParcelableCompat(ARG_DESTINATION, RecipientId::class.java) ?: return Recipient.live(recipientId).observe(viewLifecycleOwner) { recipient -> this.recipient = recipient @@ -322,12 +331,20 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a private const val ARG_INITIAL_TEXT = "arg.initial.text" private const val ARG_INITIAL_EMOJI_TOGGLE = "arg.initial.emojiToggle" + private const val ARG_VIEW_ONCE_AVAILABLE = "arg.viewOnceAvailable" + private const val ARG_DESTINATION = "arg.destination" - fun show(fragmentManager: FragmentManager, initialText: CharSequence?, startWithEmojiKeyboard: Boolean) { + const val REQUEST_KEY = "AddMessageViewModel__RequestKey" + const val RESULT_INCREMENT_VIEW_ONCE_STATE = "AddMessageViewModel_IncrementViewOnceState" + const val RESULT_MESSAGE = "AddMessageViewModel__Message" + + fun show(fragmentManager: FragmentManager, initialText: CharSequence?, startWithEmojiKeyboard: Boolean, isViewOnceAvailable: Boolean, destination: RecipientId?) { AddMessageDialogFragment().apply { arguments = Bundle().apply { putCharSequence(ARG_INITIAL_TEXT, initialText) putBoolean(ARG_INITIAL_EMOJI_TOGGLE, startWithEmojiKeyboard) + putBoolean(ARG_VIEW_ONCE_AVAILABLE, isViewOnceAvailable) + putParcelable(ARG_DESTINATION, destination) } }.show(fragmentManager, TAG) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageViewModel.kt new file mode 100644 index 0000000000..da4e57368d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/AddMessageViewModel.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2026 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.mediasend.v2.review + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch +import org.signal.core.util.BreakIteratorCompat + +class AddMessageViewModel(initialMessage: CharSequence?) : ViewModel() { + + var message: CharSequence? = initialMessage + var isViewOnce: Boolean = false + + private val addAMessageUpdatePublisher = Channel(Channel.CONFLATED) + + fun watchAddAMessageCount(): Flow { + return addAMessageUpdatePublisher + .receiveAsFlow() + .map { + val iterator = BreakIteratorCompat.getInstance() + iterator.setText(it) + AddMessageCharacterCount(iterator.countBreaks()) + } + .flowOn(Dispatchers.IO) + } + + fun updateAddAMessageCount(input: CharSequence?) { + viewModelScope.launch { + addAMessageUpdatePublisher.send(input ?: "") + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt index 3f6be8de54..cc8af72207 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt @@ -125,6 +125,15 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul disposables.bindTo(viewLifecycleOwner) + parentFragmentManager.setFragmentResultListener(AddMessageDialogFragment.REQUEST_KEY, viewLifecycleOwner) { _, bundle -> + if (bundle.getBoolean(AddMessageDialogFragment.RESULT_INCREMENT_VIEW_ONCE_STATE)) { + sharedViewModel.setMessage(null) + sharedViewModel.incrementViewOnceState() + } else { + sharedViewModel.setMessage(bundle.getCharSequence(AddMessageDialogFragment.RESULT_MESSAGE, null)) + } + } + callback = requireListener() drawToolButton = view.findViewById(R.id.draw_tool) @@ -300,11 +309,27 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul } emojiButton.setOnClickListener { - AddMessageDialogFragment.show(parentFragmentManager, sharedViewModel.state.value?.message, true) + sharedViewModel.state.value?.let { state -> + AddMessageDialogFragment.show( + parentFragmentManager, + state.message, + true, + state.selectedMedia.size == 1 && !state.isStory && !MediaUtil.isDocumentType(state.focusedMedia?.contentType), + sharedViewModel.destination.getRecipientSearchKey()?.recipientId + ) + } } addMessageButton.setOnClickListener { - AddMessageDialogFragment.show(parentFragmentManager, sharedViewModel.state.value?.message, false) + sharedViewModel.state.value?.let { state -> + AddMessageDialogFragment.show( + parentFragmentManager, + state.message, + false, + state.selectedMedia.size == 1 && !state.isStory && !MediaUtil.isDocumentType(state.focusedMedia?.contentType), + sharedViewModel.destination.getRecipientSearchKey()?.recipientId + ) + } } if (sharedViewModel.isReply) {