diff --git a/changelog.d/7895.bugfix b/changelog.d/7895.bugfix new file mode 100644 index 0000000000..ccde7a554f --- /dev/null +++ b/changelog.d/7895.bugfix @@ -0,0 +1 @@ +Send voice message should not be allowed during a voice broadcast recording diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 38aecaf215..8b51a6f95b 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3097,6 +3097,8 @@ Cannot play this voice message Cannot record a voice message Cannot reply or edit while voice message is active + Cannot start voice message + You can’t start a voice message as you are currently recording a live broadcast. Please end your live broadcast in order to start recording a voice message Voice Message (%1$s) %1$s, %2$s, %3$s diff --git a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt index c1e201cfc4..13f8997452 100644 --- a/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt +++ b/vector/src/main/java/im/vector/app/core/error/ErrorFormatter.kt @@ -151,6 +151,7 @@ class DefaultErrorFormatter @Inject constructor( return when (throwable) { is VoiceFailure.UnableToPlay -> stringProvider.getString(R.string.error_voice_message_unable_to_play) is VoiceFailure.UnableToRecord -> stringProvider.getString(R.string.error_voice_message_unable_to_record) + is VoiceFailure.VoiceBroadcastInProgress -> stringProvider.getString(R.string.error_voice_message_broadcast_in_progress) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt index 4849e20b6d..28c8757e6c 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt @@ -191,6 +191,8 @@ class MessageComposerFragment : VectorBaseFragment(), A is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> { if (it.throwable is VoiceFailure.UnableToRecord) { onCannotRecord() + } else if (it.throwable is VoiceFailure.VoiceBroadcastInProgress) { + displayErrorVoiceBroadcastInProgress() } showErrorInSnackbar(it.throwable) } @@ -526,6 +528,14 @@ class MessageComposerFragment : VectorBaseFragment(), A messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(VoiceMessageRecorderView.RecordingUiState.Idle)) } + private fun displayErrorVoiceBroadcastInProgress() { + MaterialAlertDialogBuilder(requireActivity()) + .setTitle(R.string.error_voice_message_broadcast_in_progress) + .setMessage(getString(R.string.error_voice_message_broadcast_in_progress_message)) + .setPositiveButton(android.R.string.ok, null) + .show() + } + private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) { composer.setTextIfDifferent("") lockSendButton = false 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 56ee9ffb5a..fc79c069fe 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 @@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer import android.text.SpannableString import androidx.lifecycle.asFlow import com.airbnb.mvrx.MavericksViewModelFactory +import com.airbnb.mvrx.withState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -28,6 +29,7 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider +import im.vector.app.core.time.Clock import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.extensions.toAnalyticsComposer import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom @@ -42,12 +44,19 @@ 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.VoiceFailure import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper +import im.vector.app.features.voicebroadcast.model.VoiceBroadcast +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase +import im.vector.app.features.voicebroadcast.voiceBroadcastId import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session @@ -74,6 +83,7 @@ import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.session.room.timeline.getRelationContent import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent import org.matrix.android.sdk.api.session.space.CreateSpaceParams +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.unwrap import timber.log.Timber @@ -88,6 +98,8 @@ class MessageComposerViewModel @AssistedInject constructor( private val audioMessageHelper: AudioMessageHelper, private val analyticsTracker: AnalyticsTracker, private val voiceBroadcastHelper: VoiceBroadcastHelper, + private val clock: Clock, + private val getVoiceBroadcastStateEventLiveUseCase: GetVoiceBroadcastStateEventLiveUseCase, ) : VectorViewModel(initialState) { private val room = session.getRoom(initialState.roomId) @@ -203,8 +215,11 @@ class MessageComposerViewModel @AssistedInject constructor( private fun observeVoiceBroadcast(room: Room) { room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) .asFlow() - .unwrap() - .mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState } + .map { it.getOrNull()?.asVoiceBroadcastEvent()?.voiceBroadcastId } + .flatMapLatest { voiceBroadcastId -> + voiceBroadcastId?.let { getVoiceBroadcastStateEventLiveUseCase.execute(VoiceBroadcast(it, room.roomId)) } ?: flowOf(Optional.empty()) + } + .map { it.getOrNull()?.content?.voiceBroadcastState } .setOnEach { copy(voiceBroadcastState = it) } @@ -916,10 +931,16 @@ class MessageComposerViewModel @AssistedInject constructor( } private fun handleStartRecordingVoiceMessage(room: Room) { - try { - audioMessageHelper.startRecording(room.roomId) - } catch (failure: Throwable) { - _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) + val voiceBroadcastState = withState(this) { it.voiceBroadcastState } + if (voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState.STOPPED) { + _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(VoiceFailure.VoiceBroadcastInProgress)) + } else { + try { + audioMessageHelper.startRecording(room.roomId) + setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis())) } + } catch (failure: Throwable) { + _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) + } } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt index 25764f3654..90b813d347 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceRecorderFragment.kt @@ -125,7 +125,6 @@ class VoiceRecorderFragment : VectorBaseFragment() if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) vibrate(requireContext()) - updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis())) } } diff --git a/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt b/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt index 9c4b345dc4..0a837581fd 100644 --- a/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt +++ b/vector/src/main/java/im/vector/app/features/voice/VoiceFailure.kt @@ -19,4 +19,5 @@ package im.vector.app.features.voice sealed class VoiceFailure(cause: Throwable? = null) : Throwable(cause = cause) { data class UnableToPlay(val throwable: Throwable) : VoiceFailure(throwable) data class UnableToRecord(val throwable: Throwable) : VoiceFailure(throwable) + object VoiceBroadcastInProgress : VoiceFailure() }