From b5055453d19afda45a7341c855802beeced67365 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Fri, 19 Nov 2021 14:24:04 +0000 Subject: [PATCH] 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) + } + } }