From b5055453d19afda45a7341c855802beeced67365 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Nov 2021 14:24:04 +0000 Subject: [PATCH 1/6] moving voice recording logic to the TextComposerViewModel (name to be updated) from the RoomDetailViewModel --- .../app/core/di/MavericksViewModelModule.kt | 6 +- .../home/room/detail/RoomDetailAction.kt | 9 -- .../home/room/detail/RoomDetailFragment.kt | 45 +++++----- .../home/room/detail/RoomDetailViewModel.kt | 81 ++---------------- .../detail/composer/TextComposerAction.kt | 9 ++ .../detail/composer/TextComposerViewEvents.kt | 2 + .../detail/composer/TextComposerViewModel.kt | 83 +++++++++++++++++-- 7 files changed, 119 insertions(+), 116 deletions(-) diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index c827bd0b35..69257f1f05 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -41,7 +41,7 @@ import im.vector.app.features.home.PromoteRestrictedViewModel import im.vector.app.features.home.UnknownDeviceDetectorSharedViewModel import im.vector.app.features.home.UnreadMessagesSharedViewModel import im.vector.app.features.home.room.breadcrumbs.BreadcrumbsViewModel -import im.vector.app.features.home.room.detail.composer.TextComposerViewModel +import im.vector.app.features.home.room.detail.RoomDetailViewModel import im.vector.app.features.home.room.detail.search.SearchViewModel import im.vector.app.features.home.room.detail.timeline.action.MessageActionsViewModel import im.vector.app.features.home.room.detail.timeline.edithistory.ViewEditHistoryViewModel @@ -505,8 +505,8 @@ interface MavericksViewModelModule { @Binds @IntoMap - @MavericksViewModelKey(TextComposerViewModel::class) - fun textComposerViewModelFactory(factory: TextComposerViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + @MavericksViewModelKey(RoomDetailViewModel::class) + fun roomDetailViewModelFactory(factory: RoomDetailViewModel.Factory): MavericksAssistedViewModelFactory<*, *> @Binds @IntoMap diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt index a9b9f8000b..5b05ece56f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt @@ -21,7 +21,6 @@ import android.view.View import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.call.conference.ConferenceEvent import org.matrix.android.sdk.api.session.content.ContentAttachmentData -import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent import org.matrix.android.sdk.api.session.room.model.message.MessageStickerContent import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachmentContent import org.matrix.android.sdk.api.session.room.timeline.Timeline @@ -108,12 +107,4 @@ sealed class RoomDetailAction : VectorViewModelAction { object RemoveAllFailedMessages : RoomDetailAction() data class RoomUpgradeSuccess(val replacementRoomId: String) : RoomDetailAction() - - // Voice Message - object StartRecordingVoiceMessage : RoomDetailAction() - data class EndRecordingVoiceMessage(val isCancelled: Boolean) : RoomDetailAction() - object PauseRecordingVoiceMessage : RoomDetailAction() - data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : RoomDetailAction() - object PlayOrPauseRecordingPlayback : RoomDetailAction() - data class EndAllVoiceActions(val deleteRecord: Boolean = true) : RoomDetailAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 3fcdefaf56..53a49c5180 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -241,7 +241,7 @@ class RoomDetailFragment @Inject constructor( autoCompleterFactory: AutoCompleter.Factory, private val permalinkHandler: PermalinkHandler, private val notificationDrawerManager: NotificationDrawerManager, - val roomDetailViewModelFactory: RoomDetailViewModel.Factory, + val textComposerViewModelFactory: TextComposerViewModel.Factory, private val eventHtmlRenderer: EventHtmlRenderer, private val vectorPreferences: VectorPreferences, private val colorProvider: ColorProvider, @@ -414,23 +414,24 @@ class RoomDetailFragment @Inject constructor( textComposerViewModel.observeViewEvents { when (it) { - is TextComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) - is TextComposerViewEvents.SendMessageResult -> renderSendMessageResult(it) - is TextComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message) - is TextComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it) - is TextComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it) - is TextComposerViewEvents.OpenRoomMemberProfile -> openRoomMemberProfile(it.userId) - }.exhaustive - } - - roomDetailViewModel.observeViewEvents { - when (it) { - is RoomDetailViewEvents.Failure -> { + is TextComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) + is TextComposerViewEvents.SendMessageResult -> renderSendMessageResult(it) + is TextComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message) + is TextComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it) + is TextComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it) + is TextComposerViewEvents.OpenRoomMemberProfile -> openRoomMemberProfile(it.userId) + is TextComposerViewEvents.VoicePlaybackOrRecordingFailure -> { if (it.throwable is VoiceFailure.UnableToRecord) { onCannotRecord() } showErrorInSnackbar(it.throwable) } + }.exhaustive + } + + roomDetailViewModel.observeViewEvents { + when (it) { + is RoomDetailViewEvents.Failure -> showErrorInSnackbar(it.throwable) is RoomDetailViewEvents.OnNewTimelineEvents -> scrollOnNewMessageCallback.addNewTimelineEventIds(it.eventIds) is RoomDetailViewEvents.ActionSuccess -> displayRoomDetailActionSuccess(it) is RoomDetailViewEvents.ActionFailure -> displayRoomDetailActionFailure(it) @@ -698,18 +699,18 @@ class RoomDetailFragment @Inject constructor( override fun onVoiceRecordingStarted() { if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { - roomDetailViewModel.handle(RoomDetailAction.StartRecordingVoiceMessage) + textComposerViewModel.handle(TextComposerAction.StartRecordingVoiceMessage) vibrate(requireContext()) updateRecordingUiState(RecordingUiState.Started) } } override fun onVoicePlaybackButtonClicked() { - roomDetailViewModel.handle(RoomDetailAction.PlayOrPauseRecordingPlayback) + textComposerViewModel.handle(TextComposerAction.PlayOrPauseRecordingPlayback) } override fun onVoiceRecordingCancelled() { - roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled = true)) + textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = true)) updateRecordingUiState(RecordingUiState.Cancelled) } @@ -722,22 +723,22 @@ class RoomDetailFragment @Inject constructor( } override fun onSendVoiceMessage() { - roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled = false)) + textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = false)) updateRecordingUiState(RecordingUiState.None) } override fun onDeleteVoiceMessage() { - roomDetailViewModel.handle(RoomDetailAction.EndRecordingVoiceMessage(isCancelled = true)) + textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = true)) updateRecordingUiState(RecordingUiState.None) } override fun onRecordingLimitReached() { - roomDetailViewModel.handle(RoomDetailAction.PauseRecordingVoiceMessage) + textComposerViewModel.handle(TextComposerAction.PauseRecordingVoiceMessage) updateRecordingUiState(RecordingUiState.Playback) } override fun onRecordingWaveformClicked() { - roomDetailViewModel.handle(RoomDetailAction.PauseRecordingVoiceMessage) + textComposerViewModel.handle(TextComposerAction.PauseRecordingVoiceMessage) updateRecordingUiState(RecordingUiState.Playback) } @@ -1136,7 +1137,7 @@ class RoomDetailFragment @Inject constructor( textComposerViewModel.handle(TextComposerAction.SaveDraft(views.composerLayout.text.toString())) // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. - roomDetailViewModel.handle(RoomDetailAction.EndAllVoiceActions(deleteRecord = false)) + textComposerViewModel.handle(TextComposerAction.EndAllVoiceActions(deleteRecord = false)) views.voiceMessageRecorderView.display(RecordingUiState.None) } @@ -1883,7 +1884,7 @@ class RoomDetailFragment @Inject constructor( } override fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent) { - roomDetailViewModel.handle(RoomDetailAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent)) + textComposerViewModel.handle(TextComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent)) } private fun onShareActionClicked(action: EventSharedAction.Share) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index d846a1d1f8..6de710d849 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -21,24 +21,23 @@ import androidx.annotation.IdRes import androidx.lifecycle.asFlow import com.airbnb.mvrx.Async import com.airbnb.mvrx.Fail -import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.Uninitialized -import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.BuildConfig import im.vector.app.R +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.exhaustive import im.vector.app.core.flow.chunk import im.vector.app.core.mvrx.runCatchingToAsync import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.BehaviorDataSource -import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.call.conference.ConferenceEvent import im.vector.app.features.call.conference.JitsiActiveConferenceHolder import im.vector.app.features.call.conference.JitsiService @@ -47,7 +46,6 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.createdirect.DirectRoomHelper import im.vector.app.features.crypto.keysrequest.OutboundSessionKeySharingStrategy import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider -import im.vector.app.features.home.room.detail.composer.VoiceMessageHelper import im.vector.app.features.home.room.detail.sticker.StickerPickerActionHandler import im.vector.app.features.home.room.detail.timeline.factory.TimelineFactory import im.vector.app.features.home.room.detail.timeline.url.PreviewUrlRetriever @@ -56,7 +54,6 @@ import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.settings.VectorPreferences -import im.vector.app.features.voice.VoicePlayerHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.collect @@ -116,8 +113,6 @@ class RoomDetailViewModel @AssistedInject constructor( private val directRoomHelper: DirectRoomHelper, private val jitsiService: JitsiService, private val activeConferenceHolder: JitsiActiveConferenceHolder, - private val voiceMessageHelper: VoiceMessageHelper, - private val voicePlayerHelper: VoicePlayerHelper, timelineFactory: TimelineFactory ) : VectorViewModel(initialState), Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener { @@ -144,22 +139,12 @@ class RoomDetailViewModel @AssistedInject constructor( private var prepareToEncrypt: Async = Uninitialized @AssistedFactory - interface Factory { - fun create(initialState: RoomDetailViewState): RoomDetailViewModel + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: RoomDetailViewState): RoomDetailViewModel } - /** - * Can't use the hiltMaverick here because some dependencies are injected here and in fragment but they don't share the graph. - */ - companion object : MavericksViewModelFactory { - + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { const val PAGINATION_COUNT = 50 - - @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: RoomDetailViewState): RoomDetailViewModel { - val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.roomDetailViewModelFactory.create(state) - } } init { @@ -343,12 +328,6 @@ class RoomDetailViewModel @AssistedInject constructor( is RoomDetailAction.DoNotShowPreviewUrlFor -> handleDoNotShowPreviewUrlFor(action) RoomDetailAction.RemoveAllFailedMessages -> handleRemoveAllFailedMessages() RoomDetailAction.ResendAll -> handleResendAll() - RoomDetailAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage() - is RoomDetailAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled) - is RoomDetailAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action) - RoomDetailAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage() - RoomDetailAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback() - is RoomDetailAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord) is RoomDetailAction.RoomUpgradeSuccess -> { setState { copy(joinUpgradedRoomAsync = Success(action.replacementRoomId)) @@ -612,56 +591,6 @@ class RoomDetailViewModel @AssistedInject constructor( } } - private fun handleStartRecordingVoiceMessage() { - try { - voiceMessageHelper.startRecording() - } catch (failure: Throwable) { - _viewEvents.post(RoomDetailViewEvents.Failure(failure)) - } - } - - private fun handleEndRecordingVoiceMessage(isCancelled: Boolean) { - voiceMessageHelper.stopPlayback() - if (isCancelled) { - voiceMessageHelper.deleteRecording() - } else { - voiceMessageHelper.stopRecording()?.let { audioType -> - if (audioType.duration > 1000) { - room.sendMedia(audioType.toContentAttachmentData(), false, emptySet()) - } else { - voiceMessageHelper.deleteRecording() - } - } - } - } - - private fun handlePlayOrPauseVoicePlayback(action: RoomDetailAction.PlayOrPauseVoicePlayback) { - viewModelScope.launch(Dispatchers.IO) { - try { - // Download can fail - val audioFile = session.fileService().downloadFile(action.messageAudioContent) - // Conversion can fail, fallback to the original file in this case and let the player fail for us - val convertedFile = voicePlayerHelper.convertFile(audioFile) ?: audioFile - // Play can fail - voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile) - } catch (failure: Throwable) { - _viewEvents.post(RoomDetailViewEvents.Failure(failure)) - } - } - } - - private fun handlePlayOrPauseRecordingPlayback() { - voiceMessageHelper.startOrPauseRecordingPlayback() - } - - private fun handleEndAllVoiceActions(deleteRecord: Boolean) { - voiceMessageHelper.stopAllVoiceActions(deleteRecord) - } - - private fun handlePauseRecordingVoiceMessage() { - voiceMessageHelper.pauseRecording() - } - private fun isIntegrationEnabled() = session.integrationManagerService().isIntegrationEnabled() fun isMenuItemVisible(@IdRes itemId: Int): Boolean = com.airbnb.mvrx.withState(this) { state -> diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt index 4f85b78226..1f61351755 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt @@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.composer import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent sealed class TextComposerAction : VectorViewModelAction { data class SaveDraft(val draft: String) : TextComposerAction() @@ -28,5 +29,13 @@ sealed class TextComposerAction : VectorViewModelAction { data class EnterRegularMode(val text: String, val fromSharing: Boolean) : TextComposerAction() data class UserIsTyping(val isTyping: Boolean) : TextComposerAction() data class OnTextChanged(val text: CharSequence) : TextComposerAction() + + // Voice Message data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : TextComposerAction() + object StartRecordingVoiceMessage : TextComposerAction() + data class EndRecordingVoiceMessage(val isCancelled: Boolean) : TextComposerAction() + object PauseRecordingVoiceMessage : TextComposerAction() + data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : TextComposerAction() + object PlayOrPauseRecordingPlayback : TextComposerAction() + data class EndAllVoiceActions(val deleteRecord: Boolean = true) : TextComposerAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt index ff4a09ad71..2b1d4dcf84 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt @@ -42,4 +42,6 @@ sealed class TextComposerViewEvents : VectorViewEvents { object SlashCommandNotImplemented : SendMessageResult() data class ShowRoomUpgradeDialog(val newVersion: String, val isPublic: Boolean) : TextComposerViewEvents() + + data class VoicePlaybackOrRecordingFailure(val throwable: Throwable) : TextComposerViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt index 2ff8ef6618..1541090958 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt @@ -16,24 +16,27 @@ package im.vector.app.features.home.room.detail.composer +import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.R -import im.vector.app.core.di.MavericksAssistedViewModelFactory -import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.features.attachments.toContentAttachmentData import im.vector.app.features.command.CommandParser import im.vector.app.features.command.ParsedCommand import im.vector.app.features.home.room.detail.ChatEffect +import im.vector.app.features.home.room.detail.RoomDetailFragment import im.vector.app.features.home.room.detail.composer.rainbow.RainbowGenerator import im.vector.app.features.home.room.detail.toMessageType import im.vector.app.features.powerlevel.PowerLevelsFlowFactory import im.vector.app.features.session.coroutineScope import im.vector.app.features.settings.VectorPreferences +import im.vector.app.features.voice.VoicePlayerHelper import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.commonmark.parser.Parser @@ -60,7 +63,9 @@ class TextComposerViewModel @AssistedInject constructor( private val session: Session, private val stringProvider: StringProvider, private val vectorPreferences: VectorPreferences, - private val rainbowGenerator: RainbowGenerator + private val rainbowGenerator: RainbowGenerator, + private val voiceMessageHelper: VoiceMessageHelper, + private val voicePlayerHelper: VoicePlayerHelper ) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! @@ -86,6 +91,12 @@ class TextComposerViewModel @AssistedInject constructor( is TextComposerAction.UserIsTyping -> handleUserIsTyping(action) is TextComposerAction.OnTextChanged -> handleOnTextChanged(action) is TextComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action) + TextComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage() + is TextComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled) + is TextComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action) + TextComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage() + TextComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback() + is TextComposerAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord) } } @@ -688,6 +699,56 @@ class TextComposerViewModel @AssistedInject constructor( } } + private fun handleStartRecordingVoiceMessage() { + try { + voiceMessageHelper.startRecording() + } catch (failure: Throwable) { + _viewEvents.post(TextComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) + } + } + + private fun handleEndRecordingVoiceMessage(isCancelled: Boolean) { + voiceMessageHelper.stopPlayback() + if (isCancelled) { + voiceMessageHelper.deleteRecording() + } else { + voiceMessageHelper.stopRecording()?.let { audioType -> + if (audioType.duration > 1000) { + room.sendMedia(audioType.toContentAttachmentData(), false, emptySet()) + } else { + voiceMessageHelper.deleteRecording() + } + } + } + } + + private fun handlePlayOrPauseVoicePlayback(action: TextComposerAction.PlayOrPauseVoicePlayback) { + viewModelScope.launch(Dispatchers.IO) { + try { + // Download can fail + val audioFile = session.fileService().downloadFile(action.messageAudioContent) + // Conversion can fail, fallback to the original file in this case and let the player fail for us + val convertedFile = voicePlayerHelper.convertFile(audioFile) ?: audioFile + // Play can fail + voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile) + } catch (failure: Throwable) { + _viewEvents.post(TextComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) + } + } + } + + private fun handlePlayOrPauseRecordingPlayback() { + voiceMessageHelper.startOrPauseRecordingPlayback() + } + + private fun handleEndAllVoiceActions(deleteRecord: Boolean) { + voiceMessageHelper.stopAllVoiceActions(deleteRecord) + } + + private fun handlePauseRecordingVoiceMessage() { + voiceMessageHelper.pauseRecording() + } + private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) { _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) viewModelScope.launch { @@ -703,9 +764,19 @@ class TextComposerViewModel @AssistedInject constructor( } @AssistedFactory - interface Factory : MavericksAssistedViewModelFactory { - override fun create(initialState: TextComposerViewState): TextComposerViewModel + interface Factory { + fun create(initialState: TextComposerViewState): TextComposerViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + /** + * Can't use the hiltMaverick here because some dependencies are injected here and in fragment but they don't share the graph. + */ + companion object : MavericksViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel { + val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment() + return fragment.textComposerViewModelFactory.create(state) + } + } } From 8837640b5a8748ec5a584873e5f457d79935e7aa Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Nov 2021 14:34:13 +0000 Subject: [PATCH 2/6] renaming text composer files to message composer --- .../composer/{TextComposerAction.kt => MessageComposerAction.kt} | 0 .../composer/{TextComposerView.kt => MessageComposerView.kt} | 0 .../{TextComposerViewEvents.kt => MessageComposerViewEvents.kt} | 0 .../{TextComposerViewModel.kt => MessageComposerViewModel.kt} | 0 .../{TextComposerViewState.kt => MessageComposerViewState.kt} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename vector/src/main/java/im/vector/app/features/home/room/detail/composer/{TextComposerAction.kt => MessageComposerAction.kt} (100%) rename vector/src/main/java/im/vector/app/features/home/room/detail/composer/{TextComposerView.kt => MessageComposerView.kt} (100%) rename vector/src/main/java/im/vector/app/features/home/room/detail/composer/{TextComposerViewEvents.kt => MessageComposerViewEvents.kt} (100%) rename vector/src/main/java/im/vector/app/features/home/room/detail/composer/{TextComposerViewModel.kt => MessageComposerViewModel.kt} (100%) rename vector/src/main/java/im/vector/app/features/home/room/detail/composer/{TextComposerViewState.kt => MessageComposerViewState.kt} (100%) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerAction.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerView.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewEvents.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewModel.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt similarity index 100% rename from vector/src/main/java/im/vector/app/features/home/room/detail/composer/TextComposerViewState.kt rename to vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt From f140dbc0a0434ba7cc3c6fb8e7332ab4e6208b1f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Nov 2021 14:34:42 +0000 Subject: [PATCH 3/6] update text composer classes to message composer (as they include voice not just text) --- .../home/room/detail/RoomDetailFragment.kt | 116 ++++++------- .../detail/composer/MessageComposerAction.kt | 32 ++-- .../detail/composer/MessageComposerView.kt | 2 +- .../composer/MessageComposerViewEvents.kt | 14 +- .../composer/MessageComposerViewModel.kt | 154 +++++++++--------- .../composer/MessageComposerViewState.kt | 2 +- .../main/res/layout/fragment_room_detail.xml | 2 +- 7 files changed, 161 insertions(+), 161 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 53a49c5180..270f467ff1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -133,11 +133,11 @@ import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivit import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.detail.composer.SendMode -import im.vector.app.features.home.room.detail.composer.TextComposerAction -import im.vector.app.features.home.room.detail.composer.TextComposerView -import im.vector.app.features.home.room.detail.composer.TextComposerViewEvents -import im.vector.app.features.home.room.detail.composer.TextComposerViewModel -import im.vector.app.features.home.room.detail.composer.TextComposerViewState +import im.vector.app.features.home.room.detail.composer.MessageComposerAction +import im.vector.app.features.home.room.detail.composer.MessageComposerView +import im.vector.app.features.home.room.detail.composer.MessageComposerViewEvents +import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel +import im.vector.app.features.home.room.detail.composer.MessageComposerViewState import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet @@ -241,7 +241,7 @@ class RoomDetailFragment @Inject constructor( autoCompleterFactory: AutoCompleter.Factory, private val permalinkHandler: PermalinkHandler, private val notificationDrawerManager: NotificationDrawerManager, - val textComposerViewModelFactory: TextComposerViewModel.Factory, + val messageComposerViewModelFactory: MessageComposerViewModel.Factory, private val eventHtmlRenderer: EventHtmlRenderer, private val vectorPreferences: VectorPreferences, private val colorProvider: ColorProvider, @@ -294,7 +294,7 @@ class RoomDetailFragment @Inject constructor( autoCompleterFactory.create(roomDetailArgs.roomId) } private val roomDetailViewModel: RoomDetailViewModel by fragmentViewModel() - private val textComposerViewModel: TextComposerViewModel by fragmentViewModel() + private val messageComposerViewModel: MessageComposerViewModel by fragmentViewModel() private val debouncer = Debouncer(createUIHandler()) private lateinit var scrollOnNewMessageCallback: ScrollOnNewMessageCallback @@ -387,7 +387,7 @@ class RoomDetailFragment @Inject constructor( updateJumpToReadMarkerViewVisibility() } - textComposerViewModel.onEach(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage) { mode, canSend -> + messageComposerViewModel.onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage) { mode, canSend -> if (!canSend) { return@onEach } @@ -412,15 +412,15 @@ class RoomDetailFragment @Inject constructor( ) } - textComposerViewModel.observeViewEvents { + messageComposerViewModel.observeViewEvents { when (it) { - is TextComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) - is TextComposerViewEvents.SendMessageResult -> renderSendMessageResult(it) - is TextComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message) - is TextComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it) - is TextComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it) - is TextComposerViewEvents.OpenRoomMemberProfile -> openRoomMemberProfile(it.userId) - is TextComposerViewEvents.VoicePlaybackOrRecordingFailure -> { + is MessageComposerViewEvents.JoinRoomCommandSuccess -> handleJoinedToAnotherRoom(it) + is MessageComposerViewEvents.SendMessageResult -> renderSendMessageResult(it) + is MessageComposerViewEvents.ShowMessage -> showSnackWithMessage(it.message) + is MessageComposerViewEvents.ShowRoomUpgradeDialog -> handleShowRoomUpgradeDialog(it) + is MessageComposerViewEvents.AnimateSendButtonVisibility -> handleSendButtonVisibilityChanged(it) + is MessageComposerViewEvents.OpenRoomMemberProfile -> openRoomMemberProfile(it.userId) + is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> { if (it.throwable is VoiceFailure.UnableToRecord) { onCannotRecord() } @@ -471,7 +471,7 @@ class RoomDetailFragment @Inject constructor( } } - private fun handleSendButtonVisibilityChanged(event: TextComposerViewEvents.AnimateSendButtonVisibility) { + private fun handleSendButtonVisibilityChanged(event: MessageComposerViewEvents.AnimateSendButtonVisibility) { if (event.isVisible) { views.voiceMessageRecorderView.isVisible = false views.composerLayout.views.sendButton.alpha = 0f @@ -507,7 +507,7 @@ class RoomDetailFragment @Inject constructor( private fun onCannotRecord() { // Update the UI, cancel the animation - textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingUiStateChanged(RecordingUiState.None)) + messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(RecordingUiState.None)) } private fun acceptIncomingCall(event: RoomDetailViewEvents.DisplayAndAcceptCall) { @@ -526,7 +526,7 @@ class RoomDetailFragment @Inject constructor( JoinReplacementRoomBottomSheet().show(childFragmentManager, tag) } - private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: TextComposerViewEvents.ShowRoomUpgradeDialog) { + private fun handleShowRoomUpgradeDialog(roomDetailViewEvents: MessageComposerViewEvents.ShowRoomUpgradeDialog) { val tag = MigrateRoomBottomSheet::javaClass.name MigrateRoomBottomSheet.newInstance(roomDetailArgs.roomId, roomDetailViewEvents.newVersion) .show(parentFragmentManager, tag) @@ -699,18 +699,18 @@ class RoomDetailFragment @Inject constructor( override fun onVoiceRecordingStarted() { if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { - textComposerViewModel.handle(TextComposerAction.StartRecordingVoiceMessage) + messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) vibrate(requireContext()) updateRecordingUiState(RecordingUiState.Started) } } override fun onVoicePlaybackButtonClicked() { - textComposerViewModel.handle(TextComposerAction.PlayOrPauseRecordingPlayback) + messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseRecordingPlayback) } override fun onVoiceRecordingCancelled() { - textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = true)) + messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true)) updateRecordingUiState(RecordingUiState.Cancelled) } @@ -723,27 +723,27 @@ class RoomDetailFragment @Inject constructor( } override fun onSendVoiceMessage() { - textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = false)) + messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = false)) updateRecordingUiState(RecordingUiState.None) } override fun onDeleteVoiceMessage() { - textComposerViewModel.handle(TextComposerAction.EndRecordingVoiceMessage(isCancelled = true)) + messageComposerViewModel.handle(MessageComposerAction.EndRecordingVoiceMessage(isCancelled = true)) updateRecordingUiState(RecordingUiState.None) } override fun onRecordingLimitReached() { - textComposerViewModel.handle(TextComposerAction.PauseRecordingVoiceMessage) + messageComposerViewModel.handle(MessageComposerAction.PauseRecordingVoiceMessage) updateRecordingUiState(RecordingUiState.Playback) } override fun onRecordingWaveformClicked() { - textComposerViewModel.handle(TextComposerAction.PauseRecordingVoiceMessage) + messageComposerViewModel.handle(MessageComposerAction.PauseRecordingVoiceMessage) updateRecordingUiState(RecordingUiState.Playback) } private fun updateRecordingUiState(state: RecordingUiState) { - textComposerViewModel.handle(TextComposerAction.OnVoiceRecordingUiStateChanged(state)) + messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(state)) } } } @@ -820,7 +820,7 @@ class RoomDetailFragment @Inject constructor( .show() } - private fun handleJoinedToAnotherRoom(action: TextComposerViewEvents.JoinRoomCommandSuccess) { + private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) { views.composerLayout.setTextIfDifferent("") lockSendButton = false navigator.openRoom(vectorBaseActivity, action.roomId) @@ -829,7 +829,7 @@ class RoomDetailFragment @Inject constructor( private fun handleShareData() { when (val sharedData = roomDetailArgs.sharedData) { is SharedData.Text -> { - textComposerViewModel.handle(TextComposerAction.EnterRegularMode(sharedData.text, fromSharing = true)) + messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(sharedData.text, fromSharing = true)) } is SharedData.Attachments -> { // open share edition @@ -1134,10 +1134,10 @@ class RoomDetailFragment @Inject constructor( notificationDrawerManager.setCurrentRoom(null) - textComposerViewModel.handle(TextComposerAction.SaveDraft(views.composerLayout.text.toString())) + messageComposerViewModel.handle(MessageComposerAction.SaveDraft(views.composerLayout.text.toString())) // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. - textComposerViewModel.handle(TextComposerAction.EndAllVoiceActions(deleteRecord = false)) + messageComposerViewModel.handle(MessageComposerAction.EndAllVoiceActions(deleteRecord = false)) views.voiceMessageRecorderView.display(RecordingUiState.None) } @@ -1253,12 +1253,12 @@ class RoomDetailFragment @Inject constructor( override fun performQuickReplyOnHolder(model: EpoxyModel<*>) { (model as? AbsMessageItem)?.attributes?.informationData?.let { val eventId = it.eventId - textComposerViewModel.handle(TextComposerAction.EnterReplyMode(eventId, views.composerLayout.text.toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(eventId, views.composerLayout.text.toString())) } } override fun canSwipeModel(model: EpoxyModel<*>): Boolean { - val canSendMessage = withState(textComposerViewModel) { + val canSendMessage = withState(messageComposerViewModel) { it.canSendMessage } if (!canSendMessage) { @@ -1347,7 +1347,7 @@ class RoomDetailFragment @Inject constructor( views.composerLayout.views.composerEmojiButton.isVisible = vectorPreferences.showEmojiKeyboard() - views.composerLayout.callback = object : TextComposerView.Callback { + views.composerLayout.callback = object : MessageComposerView.Callback { override fun onAddAttachment() { if (!::attachmentTypeSelector.isInitialized) { attachmentTypeSelector = AttachmentTypeSelectorView(vectorBaseActivity, vectorBaseActivity.layoutInflater, this@RoomDetailFragment) @@ -1360,7 +1360,7 @@ class RoomDetailFragment @Inject constructor( } override fun onCloseRelatedMessage() { - textComposerViewModel.handle(TextComposerAction.EnterRegularMode(views.composerLayout.text.toString(), false)) + messageComposerViewModel.handle(MessageComposerAction.EnterRegularMode(views.composerLayout.text.toString(), false)) } override fun onRichContentSelected(contentUri: Uri): Boolean { @@ -1368,7 +1368,7 @@ class RoomDetailFragment @Inject constructor( } override fun onTextChanged(text: CharSequence) { - textComposerViewModel.handle(TextComposerAction.OnTextChanged(text)) + messageComposerViewModel.handle(MessageComposerAction.OnTextChanged(text)) } } } @@ -1382,7 +1382,7 @@ class RoomDetailFragment @Inject constructor( // We collapse ASAP, if not there will be a slight annoying delay views.composerLayout.collapse(true) lockSendButton = true - textComposerViewModel.handle(TextComposerAction.SendMessage(text, vectorPreferences.isMarkdownEnabled())) + messageComposerViewModel.handle(MessageComposerAction.SendMessage(text, vectorPreferences.isMarkdownEnabled())) emojiPopup.dismiss() } } @@ -1394,7 +1394,7 @@ class RoomDetailFragment @Inject constructor( .map { it.isNotEmpty() } .onEach { Timber.d("Typing: User is typing: $it") - textComposerViewModel.handle(TextComposerAction.UserIsTyping(it)) + messageComposerViewModel.handle(MessageComposerAction.UserIsTyping(it)) } .launchIn(viewLifecycleOwner.lifecycleScope) @@ -1414,7 +1414,7 @@ class RoomDetailFragment @Inject constructor( return isHandled } - override fun invalidate() = withState(roomDetailViewModel, textComposerViewModel) { mainState, textComposerState -> + override fun invalidate() = withState(roomDetailViewModel, messageComposerViewModel) { mainState, messageComposerState -> invalidateOptionsMenu() val summary = mainState.asyncRoomSummary() renderToolbar(summary, mainState.formattedTypingUsers) @@ -1431,13 +1431,13 @@ class RoomDetailFragment @Inject constructor( timelineEventController.update(mainState) lazyLoadedViews.inviteView(false)?.isVisible = false if (mainState.tombstoneEvent == null) { - views.composerLayout.isInvisible = !textComposerState.isComposerVisible - views.voiceMessageRecorderView.isVisible = textComposerState.isVoiceMessageRecorderVisible - views.composerLayout.views.sendButton.isInvisible = !textComposerState.isSendButtonVisible - views.voiceMessageRecorderView.display(textComposerState.voiceRecordingUiState) + views.composerLayout.isInvisible = !messageComposerState.isComposerVisible + views.voiceMessageRecorderView.isVisible = messageComposerState.isVoiceMessageRecorderVisible + views.composerLayout.views.sendButton.isInvisible = !messageComposerState.isSendButtonVisible + views.voiceMessageRecorderView.display(messageComposerState.voiceRecordingUiState) views.composerLayout.setRoomEncrypted(summary.isEncrypted) // views.composerLayout.alwaysShowSendButton = false - if (textComposerState.canSendMessage) { + if (messageComposerState.canSendMessage) { views.notificationAreaView.render(NotificationAreaView.State.Hidden) } else { views.notificationAreaView.render(NotificationAreaView.State.NoPermissionToPost) @@ -1494,27 +1494,27 @@ class RoomDetailFragment @Inject constructor( } } - private fun renderSendMessageResult(sendMessageResult: TextComposerViewEvents.SendMessageResult) { + private fun renderSendMessageResult(sendMessageResult: MessageComposerViewEvents.SendMessageResult) { when (sendMessageResult) { - is TextComposerViewEvents.SlashCommandLoading -> { + is MessageComposerViewEvents.SlashCommandLoading -> { showLoading(null) } - is TextComposerViewEvents.SlashCommandError -> { + is MessageComposerViewEvents.SlashCommandError -> { displayCommandError(getString(R.string.command_problem_with_parameters, sendMessageResult.command.command)) } - is TextComposerViewEvents.SlashCommandUnknown -> { + is MessageComposerViewEvents.SlashCommandUnknown -> { displayCommandError(getString(R.string.unrecognized_command, sendMessageResult.command)) } - is TextComposerViewEvents.SlashCommandResultOk -> { + is MessageComposerViewEvents.SlashCommandResultOk -> { dismissLoadingDialog() views.composerLayout.setTextIfDifferent("") sendMessageResult.messageRes?.let { showSnackWithMessage(getString(it)) } } - is TextComposerViewEvents.SlashCommandResultError -> { + is MessageComposerViewEvents.SlashCommandResultError -> { dismissLoadingDialog() displayCommandError(errorFormatter.toHumanReadable(sendMessageResult.throwable)) } - is TextComposerViewEvents.SlashCommandNotImplemented -> { + is MessageComposerViewEvents.SlashCommandNotImplemented -> { displayCommandError(getString(R.string.not_implemented)) } } // .exhaustive @@ -1884,7 +1884,7 @@ class RoomDetailFragment @Inject constructor( } override fun onVoiceControlButtonClicked(eventId: String, messageAudioContent: MessageAudioContent) { - textComposerViewModel.handle(TextComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent)) + messageComposerViewModel.handle(MessageComposerAction.PlayOrPauseVoicePlayback(eventId, messageAudioContent)) } private fun onShareActionClicked(action: EventSharedAction.Share) { @@ -1989,18 +1989,18 @@ class RoomDetailFragment @Inject constructor( roomDetailViewModel.handle(RoomDetailAction.UpdateQuickReactAction(action.eventId, action.clickedOn, action.add)) } is EventSharedAction.Edit -> { - if (withState(textComposerViewModel) { it.isVoiceMessageIdle }) { - textComposerViewModel.handle(TextComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) + if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) { + messageComposerViewModel.handle(MessageComposerAction.EnterEditMode(action.eventId, views.composerLayout.text.toString())) } else { requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } } is EventSharedAction.Quote -> { - textComposerViewModel.handle(TextComposerAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString())) + messageComposerViewModel.handle(MessageComposerAction.EnterQuoteMode(action.eventId, views.composerLayout.text.toString())) } is EventSharedAction.Reply -> { - if (withState(textComposerViewModel) { it.isVoiceMessageIdle }) { - textComposerViewModel.handle(TextComposerAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) + if (withState(messageComposerViewModel) { it.isVoiceMessageIdle }) { + messageComposerViewModel.handle(MessageComposerAction.EnterReplyMode(action.eventId, views.composerLayout.text.toString())) } else { requireActivity().toast(R.string.error_voice_message_cannot_reply_or_edit) } @@ -2213,7 +2213,7 @@ class RoomDetailFragment @Inject constructor( override fun onContactAttachmentReady(contactAttachment: ContactAttachment) { super.onContactAttachmentReady(contactAttachment) val formattedContact = contactAttachment.toHumanReadable() - textComposerViewModel.handle(TextComposerAction.SendMessage(formattedContact, false)) + messageComposerViewModel.handle(MessageComposerAction.SendMessage(formattedContact, false)) } private fun onViewWidgetsClicked() { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt index 1f61351755..392a52e7fa 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerAction.kt @@ -20,22 +20,22 @@ import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent -sealed class TextComposerAction : VectorViewModelAction { - data class SaveDraft(val draft: String) : TextComposerAction() - data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : TextComposerAction() - data class EnterEditMode(val eventId: String, val text: String) : TextComposerAction() - data class EnterQuoteMode(val eventId: String, val text: String) : TextComposerAction() - data class EnterReplyMode(val eventId: String, val text: String) : TextComposerAction() - data class EnterRegularMode(val text: String, val fromSharing: Boolean) : TextComposerAction() - data class UserIsTyping(val isTyping: Boolean) : TextComposerAction() - data class OnTextChanged(val text: CharSequence) : TextComposerAction() +sealed class MessageComposerAction : VectorViewModelAction { + data class SaveDraft(val draft: String) : MessageComposerAction() + data class SendMessage(val text: CharSequence, val autoMarkdown: Boolean) : MessageComposerAction() + data class EnterEditMode(val eventId: String, val text: String) : MessageComposerAction() + data class EnterQuoteMode(val eventId: String, val text: String) : MessageComposerAction() + data class EnterReplyMode(val eventId: String, val text: String) : MessageComposerAction() + data class EnterRegularMode(val text: String, val fromSharing: Boolean) : MessageComposerAction() + data class UserIsTyping(val isTyping: Boolean) : MessageComposerAction() + data class OnTextChanged(val text: CharSequence) : MessageComposerAction() // Voice Message - data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : TextComposerAction() - object StartRecordingVoiceMessage : TextComposerAction() - data class EndRecordingVoiceMessage(val isCancelled: Boolean) : TextComposerAction() - object PauseRecordingVoiceMessage : TextComposerAction() - data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : TextComposerAction() - object PlayOrPauseRecordingPlayback : TextComposerAction() - data class EndAllVoiceActions(val deleteRecord: Boolean = true) : TextComposerAction() + data class OnVoiceRecordingUiStateChanged(val uiState: VoiceMessageRecorderView.RecordingUiState) : MessageComposerAction() + object StartRecordingVoiceMessage : MessageComposerAction() + data class EndRecordingVoiceMessage(val isCancelled: Boolean) : MessageComposerAction() + object PauseRecordingVoiceMessage : MessageComposerAction() + data class PlayOrPauseVoicePlayback(val eventId: String, val messageAudioContent: MessageAudioContent) : MessageComposerAction() + object PlayOrPauseRecordingPlayback : MessageComposerAction() + data class EndAllVoiceActions(val deleteRecord: Boolean = true) : MessageComposerAction() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt index 34b3c1777c..e77ad66a9f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerView.kt @@ -36,7 +36,7 @@ import im.vector.app.databinding.ComposerLayoutBinding /** * Encapsulate the timeline composer UX. */ -class TextComposerView @JvmOverloads constructor( +class MessageComposerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : ConstraintLayout(context, attrs, defStyleAttr) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt index 2b1d4dcf84..07c58c9196 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewEvents.kt @@ -20,13 +20,13 @@ import androidx.annotation.StringRes import im.vector.app.core.platform.VectorViewEvents import im.vector.app.features.command.Command -sealed class TextComposerViewEvents : VectorViewEvents { +sealed class MessageComposerViewEvents : VectorViewEvents { - data class AnimateSendButtonVisibility(val isVisible: Boolean) : TextComposerViewEvents() + data class AnimateSendButtonVisibility(val isVisible: Boolean) : MessageComposerViewEvents() - data class ShowMessage(val message: String) : TextComposerViewEvents() + data class ShowMessage(val message: String) : MessageComposerViewEvents() - abstract class SendMessageResult : TextComposerViewEvents() + abstract class SendMessageResult : MessageComposerViewEvents() object MessageSent : SendMessageResult() data class JoinRoomCommandSuccess(val roomId: String) : SendMessageResult() @@ -36,12 +36,12 @@ sealed class TextComposerViewEvents : VectorViewEvents { data class SlashCommandResultOk(@StringRes val messageRes: Int? = null) : SendMessageResult() class SlashCommandResultError(val throwable: Throwable) : SendMessageResult() - data class OpenRoomMemberProfile(val userId: String) : TextComposerViewEvents() + data class OpenRoomMemberProfile(val userId: String) : MessageComposerViewEvents() // TODO Remove object SlashCommandNotImplemented : SendMessageResult() - data class ShowRoomUpgradeDialog(val newVersion: String, val isPublic: Boolean) : TextComposerViewEvents() + data class ShowRoomUpgradeDialog(val newVersion: String, val isPublic: Boolean) : MessageComposerViewEvents() - data class VoicePlaybackOrRecordingFailure(val throwable: Throwable) : TextComposerViewEvents() + data class VoicePlaybackOrRecordingFailure(val throwable: Throwable) : MessageComposerViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index 1541090958..d0c5f6de1d 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -58,15 +58,15 @@ import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.space.CreateSpaceParams import timber.log.Timber -class TextComposerViewModel @AssistedInject constructor( - @Assisted initialState: TextComposerViewState, +class MessageComposerViewModel @AssistedInject constructor( + @Assisted initialState: MessageComposerViewState, private val session: Session, private val stringProvider: StringProvider, private val vectorPreferences: VectorPreferences, private val rainbowGenerator: RainbowGenerator, private val voiceMessageHelper: VoiceMessageHelper, private val voicePlayerHelper: VoicePlayerHelper -) : VectorViewModel(initialState) { +) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId)!! @@ -79,32 +79,32 @@ class TextComposerViewModel @AssistedInject constructor( subscribeToStateInternal() } - override fun handle(action: TextComposerAction) { + override fun handle(action: MessageComposerAction) { Timber.v("Handle action: $action") when (action) { - is TextComposerAction.EnterEditMode -> handleEnterEditMode(action) - is TextComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action) - is TextComposerAction.EnterRegularMode -> handleEnterRegularMode(action) - is TextComposerAction.EnterReplyMode -> handleEnterReplyMode(action) - is TextComposerAction.SaveDraft -> handleSaveDraft(action) - is TextComposerAction.SendMessage -> handleSendMessage(action) - is TextComposerAction.UserIsTyping -> handleUserIsTyping(action) - is TextComposerAction.OnTextChanged -> handleOnTextChanged(action) - is TextComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action) - TextComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage() - is TextComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled) - is TextComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action) - TextComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage() - TextComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback() - is TextComposerAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord) + is MessageComposerAction.EnterEditMode -> handleEnterEditMode(action) + is MessageComposerAction.EnterQuoteMode -> handleEnterQuoteMode(action) + is MessageComposerAction.EnterRegularMode -> handleEnterRegularMode(action) + is MessageComposerAction.EnterReplyMode -> handleEnterReplyMode(action) + is MessageComposerAction.SaveDraft -> handleSaveDraft(action) + is MessageComposerAction.SendMessage -> handleSendMessage(action) + is MessageComposerAction.UserIsTyping -> handleUserIsTyping(action) + is MessageComposerAction.OnTextChanged -> handleOnTextChanged(action) + is MessageComposerAction.OnVoiceRecordingUiStateChanged -> handleOnVoiceRecordingUiStateChanged(action) + MessageComposerAction.StartRecordingVoiceMessage -> handleStartRecordingVoiceMessage() + is MessageComposerAction.EndRecordingVoiceMessage -> handleEndRecordingVoiceMessage(action.isCancelled) + is MessageComposerAction.PlayOrPauseVoicePlayback -> handlePlayOrPauseVoicePlayback(action) + MessageComposerAction.PauseRecordingVoiceMessage -> handlePauseRecordingVoiceMessage() + MessageComposerAction.PlayOrPauseRecordingPlayback -> handlePlayOrPauseRecordingPlayback() + is MessageComposerAction.EndAllVoiceActions -> handleEndAllVoiceActions(action.deleteRecord) } } - private fun handleOnVoiceRecordingUiStateChanged(action: TextComposerAction.OnVoiceRecordingUiStateChanged) = setState { + private fun handleOnVoiceRecordingUiStateChanged(action: MessageComposerAction.OnVoiceRecordingUiStateChanged) = setState { copy(voiceRecordingUiState = action.uiState) } - private fun handleOnTextChanged(action: TextComposerAction.OnTextChanged) { + private fun handleOnTextChanged(action: MessageComposerAction.OnTextChanged) { setState { // Makes sure currentComposerText is upToDate when accessing further setState currentComposerText = action.text @@ -114,7 +114,7 @@ class TextComposerViewModel @AssistedInject constructor( } private fun subscribeToStateInternal() { - onEach(TextComposerViewState::sendMode, TextComposerViewState::canSendMessage, TextComposerViewState::isVoiceRecording) { _, _, _ -> + onEach(MessageComposerViewState::sendMode, MessageComposerViewState::canSendMessage, MessageComposerViewState::isVoiceRecording) { _, _, _ -> updateIsSendButtonVisibility(false) } } @@ -122,16 +122,16 @@ class TextComposerViewModel @AssistedInject constructor( private fun updateIsSendButtonVisibility(triggerAnimation: Boolean) = setState { val isSendButtonVisible = isComposerVisible && (sendMode !is SendMode.REGULAR || currentComposerText.isNotBlank()) if (this.isSendButtonVisible != isSendButtonVisible && triggerAnimation) { - _viewEvents.post(TextComposerViewEvents.AnimateSendButtonVisibility(isSendButtonVisible)) + _viewEvents.post(MessageComposerViewEvents.AnimateSendButtonVisibility(isSendButtonVisible)) } copy(isSendButtonVisible = isSendButtonVisible) } - private fun handleEnterRegularMode(action: TextComposerAction.EnterRegularMode) = setState { + private fun handleEnterRegularMode(action: MessageComposerAction.EnterRegularMode) = setState { copy(sendMode = SendMode.REGULAR(action.text, action.fromSharing)) } - private fun handleEnterEditMode(action: TextComposerAction.EnterEditMode) { + private fun handleEnterEditMode(action: MessageComposerAction.EnterEditMode) { room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent())) } } @@ -145,19 +145,19 @@ class TextComposerViewModel @AssistedInject constructor( } } - private fun handleEnterQuoteMode(action: TextComposerAction.EnterQuoteMode) { + private fun handleEnterQuoteMode(action: MessageComposerAction.EnterQuoteMode) { room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) } } } - private fun handleEnterReplyMode(action: TextComposerAction.EnterReplyMode) { + private fun handleEnterReplyMode(action: MessageComposerAction.EnterReplyMode) { room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) } } } - private fun handleSendMessage(action: TextComposerAction.SendMessage) { + private fun handleSendMessage(action: MessageComposerAction.SendMessage) { withState { state -> when (state.sendMode) { is SendMode.REGULAR -> { @@ -165,22 +165,22 @@ class TextComposerViewModel @AssistedInject constructor( is ParsedCommand.ErrorNotACommand -> { // Send the text message to the room room.sendTextMessage(action.text, autoMarkdown = action.autoMarkdown) - _viewEvents.post(TextComposerViewEvents.MessageSent) + _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } is ParsedCommand.ErrorSyntax -> { - _viewEvents.post(TextComposerViewEvents.SlashCommandError(slashCommandResult.command)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandError(slashCommandResult.command)) } is ParsedCommand.ErrorEmptySlashCommand -> { - _viewEvents.post(TextComposerViewEvents.SlashCommandUnknown("/")) + _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown("/")) } is ParsedCommand.ErrorUnknownSlashCommand -> { - _viewEvents.post(TextComposerViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandUnknown(slashCommandResult.slashCommand)) } is ParsedCommand.SendPlainText -> { // Send the text message to the room, without markdown room.sendTextMessage(slashCommandResult.message, autoMarkdown = false) - _viewEvents.post(TextComposerViewEvents.MessageSent) + _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } is ParsedCommand.ChangeRoomName -> { @@ -197,11 +197,11 @@ class TextComposerViewModel @AssistedInject constructor( } is ParsedCommand.ClearScalarToken -> { // TODO - _viewEvents.post(TextComposerViewEvents.SlashCommandNotImplemented) + _viewEvents.post(MessageComposerViewEvents.SlashCommandNotImplemented) } is ParsedCommand.SetMarkdown -> { vectorPreferences.setMarkdownEnabled(slashCommandResult.enable) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk( + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk( if (slashCommandResult.enable) R.string.markdown_has_been_enabled else R.string.markdown_has_been_disabled)) popDraft() } @@ -229,21 +229,21 @@ class TextComposerViewModel @AssistedInject constructor( } is ParsedCommand.SendEmote -> { room.sendTextMessage(slashCommandResult.message, msgType = MessageType.MSGTYPE_EMOTE, autoMarkdown = action.autoMarkdown) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendRainbow -> { slashCommandResult.message.toString().let { room.sendFormattedTextMessage(it, rainbowGenerator.generate(it)) } - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendRainbowEmote -> { slashCommandResult.message.toString().let { room.sendFormattedTextMessage(it, rainbowGenerator.generate(it), MessageType.MSGTYPE_EMOTE) } - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendSpoiler -> { @@ -251,22 +251,22 @@ class TextComposerViewModel @AssistedInject constructor( "[${stringProvider.getString(R.string.spoiler)}](${slashCommandResult.message})", "${slashCommandResult.message}" ) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendShrug -> { sendPrefixedMessage("¯\\_(ツ)_/¯", slashCommandResult.message) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendLenny -> { sendPrefixedMessage("( ͡° ͜ʖ ͡°)", slashCommandResult.message) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.SendChatEffect -> { sendChatEffect(slashCommandResult) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } is ParsedCommand.ChangeTopic -> { @@ -285,25 +285,25 @@ class TextComposerViewModel @AssistedInject constructor( handleChangeAvatarForRoomSlashCommand(slashCommandResult) } is ParsedCommand.ShowUser -> { - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) handleWhoisSlashCommand(slashCommandResult) popDraft() } is ParsedCommand.DiscardSession -> { if (room.isEncrypted()) { session.cryptoService().discardOutboundSession(room.roomId) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } else { - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) _viewEvents.post( - TextComposerViewEvents + MessageComposerViewEvents .ShowMessage(stringProvider.getString(R.string.command_description_discard_session_not_handled)) ) } } is ParsedCommand.CreateSpace -> { - _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) + _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading) viewModelScope.launch(Dispatchers.IO) { try { val params = CreateSpaceParams().apply { @@ -319,15 +319,15 @@ class TextComposerViewModel @AssistedInject constructor( true ) popDraft() - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) } catch (failure: Throwable) { - _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) } } Unit } is ParsedCommand.AddToSpace -> { - _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) + _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading) viewModelScope.launch(Dispatchers.IO) { try { session.spaceService().getSpace(slashCommandResult.spaceId) @@ -338,22 +338,22 @@ class TextComposerViewModel @AssistedInject constructor( false ) popDraft() - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) } catch (failure: Throwable) { - _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) } } Unit } is ParsedCommand.JoinSpace -> { - _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) + _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading) viewModelScope.launch(Dispatchers.IO) { try { session.spaceService().joinSpace(slashCommandResult.spaceIdOrAlias) popDraft() - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) } catch (failure: Throwable) { - _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) } } Unit @@ -363,21 +363,21 @@ class TextComposerViewModel @AssistedInject constructor( try { session.getRoom(slashCommandResult.roomId)?.leave(null) popDraft() - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) } catch (failure: Throwable) { - _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) } } Unit } is ParsedCommand.UpgradeRoom -> { _viewEvents.post( - TextComposerViewEvents.ShowRoomUpgradeDialog( + MessageComposerViewEvents.ShowRoomUpgradeDialog( slashCommandResult.newVersion, room.roomSummary()?.isPublic ?: false ) ) - _viewEvents.post(TextComposerViewEvents.SlashCommandResultOk()) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultOk()) popDraft() } }.exhaustive @@ -402,7 +402,7 @@ class TextComposerViewModel @AssistedInject constructor( Timber.w("Same message content, do not send edition") } } - _viewEvents.post(TextComposerViewEvents.MessageSent) + _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } is SendMode.QUOTE -> { @@ -423,13 +423,13 @@ class TextComposerViewModel @AssistedInject constructor( } else { room.sendFormattedTextMessage(finalText, htmlText) } - _viewEvents.post(TextComposerViewEvents.MessageSent) + _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) - _viewEvents.post(TextComposerViewEvents.MessageSent) + _viewEvents.post(MessageComposerViewEvents.MessageSent) popDraft() } } @@ -478,7 +478,7 @@ class TextComposerViewModel @AssistedInject constructor( } } - private fun handleUserIsTyping(action: TextComposerAction.UserIsTyping) { + private fun handleUserIsTyping(action: MessageComposerAction.UserIsTyping) { if (vectorPreferences.sendTypingNotifs()) { if (action.isTyping) { room.userIsTyping() @@ -506,13 +506,13 @@ class TextComposerViewModel @AssistedInject constructor( try { session.joinRoom(command.roomAlias, command.reason, emptyList()) } catch (failure: Throwable) { - _viewEvents.post(TextComposerViewEvents.SlashCommandResultError(failure)) + _viewEvents.post(MessageComposerViewEvents.SlashCommandResultError(failure)) return@launch } session.getRoomSummary(command.roomAlias) ?.roomId ?.let { - _viewEvents.post(TextComposerViewEvents.JoinRoomCommandSuccess(it)) + _viewEvents.post(MessageComposerViewEvents.JoinRoomCommandSuccess(it)) } } } @@ -659,7 +659,7 @@ class TextComposerViewModel @AssistedInject constructor( } private fun handleWhoisSlashCommand(whois: ParsedCommand.ShowUser) { - _viewEvents.post(TextComposerViewEvents.OpenRoomMemberProfile(whois.userId)) + _viewEvents.post(MessageComposerViewEvents.OpenRoomMemberProfile(whois.userId)) } private fun sendPrefixedMessage(prefix: String, message: CharSequence) { @@ -676,7 +676,7 @@ class TextComposerViewModel @AssistedInject constructor( /** * Convert a send mode to a draft and save the draft */ - private fun handleSaveDraft(action: TextComposerAction.SaveDraft) = withState { + private fun handleSaveDraft(action: MessageComposerAction.SaveDraft) = withState { session.coroutineScope.launch { when { it.sendMode is SendMode.REGULAR && !it.sendMode.fromSharing -> { @@ -703,7 +703,7 @@ class TextComposerViewModel @AssistedInject constructor( try { voiceMessageHelper.startRecording() } catch (failure: Throwable) { - _viewEvents.post(TextComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) + _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) } } @@ -722,7 +722,7 @@ class TextComposerViewModel @AssistedInject constructor( } } - private fun handlePlayOrPauseVoicePlayback(action: TextComposerAction.PlayOrPauseVoicePlayback) { + private fun handlePlayOrPauseVoicePlayback(action: MessageComposerAction.PlayOrPauseVoicePlayback) { viewModelScope.launch(Dispatchers.IO) { try { // Download can fail @@ -732,7 +732,7 @@ class TextComposerViewModel @AssistedInject constructor( // Play can fail voiceMessageHelper.startOrPausePlayback(action.eventId, convertedFile) } catch (failure: Throwable) { - _viewEvents.post(TextComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) + _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) } } } @@ -750,14 +750,14 @@ class TextComposerViewModel @AssistedInject constructor( } private fun launchSlashCommandFlowSuspendable(block: suspend () -> Unit) { - _viewEvents.post(TextComposerViewEvents.SlashCommandLoading) + _viewEvents.post(MessageComposerViewEvents.SlashCommandLoading) viewModelScope.launch { val event = try { block() popDraft() - TextComposerViewEvents.SlashCommandResultOk() + MessageComposerViewEvents.SlashCommandResultOk() } catch (failure: Throwable) { - TextComposerViewEvents.SlashCommandResultError(failure) + MessageComposerViewEvents.SlashCommandResultError(failure) } _viewEvents.post(event) } @@ -765,18 +765,18 @@ class TextComposerViewModel @AssistedInject constructor( @AssistedFactory interface Factory { - fun create(initialState: TextComposerViewState): TextComposerViewModel + fun create(initialState: MessageComposerViewState): MessageComposerViewModel } /** * Can't use the hiltMaverick here because some dependencies are injected here and in fragment but they don't share the graph. */ - companion object : MavericksViewModelFactory { + companion object : MavericksViewModelFactory { @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: TextComposerViewState): TextComposerViewModel { + override fun create(viewModelContext: ViewModelContext, state: MessageComposerViewState): MessageComposerViewModel { val fragment: RoomDetailFragment = (viewModelContext as FragmentViewModelContext).fragment() - return fragment.textComposerViewModelFactory.create(state) + return fragment.messageComposerViewModelFactory.create(state) } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index fa19d129e9..def1c8d2fb 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -42,7 +42,7 @@ sealed class SendMode(open val text: String) { data class REPLY(val timelineEvent: TimelineEvent, override val text: String) : SendMode(text) } -data class TextComposerViewState( +data class MessageComposerViewState( val roomId: String, val canSendMessage: Boolean = true, val isSendButtonVisible: Boolean = false, diff --git a/vector/src/main/res/layout/fragment_room_detail.xml b/vector/src/main/res/layout/fragment_room_detail.xml index 1b73e0e91d..356907cd4e 100644 --- a/vector/src/main/res/layout/fragment_room_detail.xml +++ b/vector/src/main/res/layout/fragment_room_detail.xml @@ -199,7 +199,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> - Date: Fri, 19 Nov 2021 14:47:52 +0000 Subject: [PATCH 4/6] fixing import ordering --- .../vector/app/features/home/room/detail/RoomDetailFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 270f467ff1..b1a6dae9dd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -132,12 +132,12 @@ import im.vector.app.features.command.Command import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity import im.vector.app.features.crypto.verification.VerificationBottomSheet import im.vector.app.features.home.AvatarRenderer -import im.vector.app.features.home.room.detail.composer.SendMode import im.vector.app.features.home.room.detail.composer.MessageComposerAction import im.vector.app.features.home.room.detail.composer.MessageComposerView import im.vector.app.features.home.room.detail.composer.MessageComposerViewEvents import im.vector.app.features.home.room.detail.composer.MessageComposerViewModel import im.vector.app.features.home.room.detail.composer.MessageComposerViewState +import im.vector.app.features.home.room.detail.composer.SendMode import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView.RecordingUiState import im.vector.app.features.home.room.detail.readreceipts.DisplayReadReceiptsBottomSheet From cca50ed6698f45cc5cb2797231e36384ba9f27e9 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Nov 2021 15:03:37 +0000 Subject: [PATCH 5/6] using project terminoloy for rendering ui states on views --- .../app/features/home/room/detail/RoomDetailFragment.kt | 4 ++-- .../room/detail/composer/voice/VoiceMessageRecorderView.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index b1a6dae9dd..25c2c0b795 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -1138,7 +1138,7 @@ class RoomDetailFragment @Inject constructor( // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. messageComposerViewModel.handle(MessageComposerAction.EndAllVoiceActions(deleteRecord = false)) - views.voiceMessageRecorderView.display(RecordingUiState.None) + views.voiceMessageRecorderView.render(RecordingUiState.None) } private val attachmentFileActivityResultLauncher = registerStartForActivityResult { @@ -1434,7 +1434,7 @@ class RoomDetailFragment @Inject constructor( views.composerLayout.isInvisible = !messageComposerState.isComposerVisible views.voiceMessageRecorderView.isVisible = messageComposerState.isVoiceMessageRecorderVisible views.composerLayout.views.sendButton.isInvisible = !messageComposerState.isSendButtonVisible - views.voiceMessageRecorderView.display(messageComposerState.voiceRecordingUiState) + views.voiceMessageRecorderView.render(messageComposerState.voiceRecordingUiState) views.composerLayout.setRoomEncrypted(summary.isEncrypted) // views.composerLayout.alwaysShowSendButton = false if (messageComposerState.canSendMessage) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index d212e800a8..14d5a58279 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -103,7 +103,7 @@ class VoiceMessageRecorderView @JvmOverloads constructor( voiceMessageViews.renderVisibilityChanged(parentChanged, visibility) } - fun display(recordingState: RecordingUiState) { + fun render(recordingState: RecordingUiState) { if (lastKnownState == recordingState) return lastKnownState = recordingState when (recordingState) { From c0f8984d165a6c5b1e26a60536837bab079d9f81 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Nov 2021 15:43:37 +0000 Subject: [PATCH 6/6] updating workaround factory doc --- .../home/room/detail/composer/MessageComposerViewModel.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt index d0c5f6de1d..b9932e32ee 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt @@ -769,7 +769,11 @@ class MessageComposerViewModel @AssistedInject constructor( } /** - * Can't use the hiltMaverick here because some dependencies are injected here and in fragment but they don't share the graph. + * We're unable to create this ViewModel with `by hiltMavericksViewModelFactory()` due to the + * VoiceMessagePlaybackTracker being ActivityScoped + * + * This factory allows us to provide the ViewModel instance from the Fragment directly + * bypassing the Singleton scope requirement */ companion object : MavericksViewModelFactory {