Merge pull request #7974 from vector-im/yosyle/voicebroadcast_block_voicemessage

This commit is contained in:
Yoan Pintas 2023-01-20 15:41:18 +00:00 committed by GitHub
commit 64f9cfef42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 43 additions and 8 deletions

1
changelog.d/7895.bugfix Normal file
View File

@ -0,0 +1 @@
Send voice message should not be allowed during a voice broadcast recording

View File

@ -3097,6 +3097,8 @@
<string name="error_voice_message_unable_to_play">Cannot play this voice message</string> <string name="error_voice_message_unable_to_play">Cannot play this voice message</string>
<string name="error_voice_message_unable_to_record">Cannot record a voice message</string> <string name="error_voice_message_unable_to_record">Cannot record a voice message</string>
<string name="error_voice_message_cannot_reply_or_edit">Cannot reply or edit while voice message is active</string> <string name="error_voice_message_cannot_reply_or_edit">Cannot reply or edit while voice message is active</string>
<string name="error_voice_message_broadcast_in_progress">Cannot start voice message</string>
<string name="error_voice_message_broadcast_in_progress_message">You cant 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</string>
<string name="voice_message_reply_content">Voice Message (%1$s)</string> <string name="voice_message_reply_content">Voice Message (%1$s)</string>
<string name="a11y_audio_message_item">%1$s, %2$s, %3$s</string> <!-- filename, duration, file size --> <string name="a11y_audio_message_item">%1$s, %2$s, %3$s</string> <!-- filename, duration, file size -->

View File

@ -151,6 +151,7 @@ class DefaultErrorFormatter @Inject constructor(
return when (throwable) { return when (throwable) {
is VoiceFailure.UnableToPlay -> stringProvider.getString(R.string.error_voice_message_unable_to_play) 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.UnableToRecord -> stringProvider.getString(R.string.error_voice_message_unable_to_record)
is VoiceFailure.VoiceBroadcastInProgress -> stringProvider.getString(R.string.error_voice_message_broadcast_in_progress)
} }
} }

View File

@ -191,6 +191,8 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> { is MessageComposerViewEvents.VoicePlaybackOrRecordingFailure -> {
if (it.throwable is VoiceFailure.UnableToRecord) { if (it.throwable is VoiceFailure.UnableToRecord) {
onCannotRecord() onCannotRecord()
} else if (it.throwable is VoiceFailure.VoiceBroadcastInProgress) {
displayErrorVoiceBroadcastInProgress()
} }
showErrorInSnackbar(it.throwable) showErrorInSnackbar(it.throwable)
} }
@ -526,6 +528,14 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
messageComposerViewModel.handle(MessageComposerAction.OnVoiceRecordingUiStateChanged(VoiceMessageRecorderView.RecordingUiState.Idle)) 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) { private fun handleJoinedToAnotherRoom(action: MessageComposerViewEvents.JoinRoomCommandSuccess) {
composer.setTextIfDifferent("") composer.setTextIfDifferent("")
lockSendButton = false lockSendButton = false

View File

@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.composer
import android.text.SpannableString import android.text.SpannableString
import androidx.lifecycle.asFlow import androidx.lifecycle.asFlow
import com.airbnb.mvrx.MavericksViewModelFactory import com.airbnb.mvrx.MavericksViewModelFactory
import com.airbnb.mvrx.withState
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject 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.extensions.getVectorLastMessageContent
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.core.resources.StringProvider 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.AnalyticsTracker
import im.vector.app.features.analytics.extensions.toAnalyticsComposer import im.vector.app.features.analytics.extensions.toAnalyticsComposer
import im.vector.app.features.analytics.extensions.toAnalyticsJoinedRoom 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.powerlevel.PowerLevelsFlowFactory
import im.vector.app.features.session.coroutineScope import im.vector.app.features.session.coroutineScope
import im.vector.app.features.settings.VectorPreferences 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.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper 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.model.asVoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
import im.vector.app.features.voicebroadcast.voiceBroadcastId
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine 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 kotlinx.coroutines.launch
import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.QueryStringValue
import org.matrix.android.sdk.api.session.Session 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.getRelationContent
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent 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.session.space.CreateSpaceParams
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.flow.flow import org.matrix.android.sdk.flow.flow
import org.matrix.android.sdk.flow.unwrap import org.matrix.android.sdk.flow.unwrap
import timber.log.Timber import timber.log.Timber
@ -88,6 +98,8 @@ class MessageComposerViewModel @AssistedInject constructor(
private val audioMessageHelper: AudioMessageHelper, private val audioMessageHelper: AudioMessageHelper,
private val analyticsTracker: AnalyticsTracker, private val analyticsTracker: AnalyticsTracker,
private val voiceBroadcastHelper: VoiceBroadcastHelper, private val voiceBroadcastHelper: VoiceBroadcastHelper,
private val clock: Clock,
private val getVoiceBroadcastStateEventLiveUseCase: GetVoiceBroadcastStateEventLiveUseCase,
) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) { ) : VectorViewModel<MessageComposerViewState, MessageComposerAction, MessageComposerViewEvents>(initialState) {
private val room = session.getRoom(initialState.roomId) private val room = session.getRoom(initialState.roomId)
@ -203,8 +215,11 @@ class MessageComposerViewModel @AssistedInject constructor(
private fun observeVoiceBroadcast(room: Room) { private fun observeVoiceBroadcast(room: Room) {
room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) room.stateService().getStateEventLive(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId))
.asFlow() .asFlow()
.unwrap() .map { it.getOrNull()?.asVoiceBroadcastEvent()?.voiceBroadcastId }
.mapNotNull { it.asVoiceBroadcastEvent()?.content?.voiceBroadcastState } .flatMapLatest { voiceBroadcastId ->
voiceBroadcastId?.let { getVoiceBroadcastStateEventLiveUseCase.execute(VoiceBroadcast(it, room.roomId)) } ?: flowOf(Optional.empty())
}
.map { it.getOrNull()?.content?.voiceBroadcastState }
.setOnEach { .setOnEach {
copy(voiceBroadcastState = it) copy(voiceBroadcastState = it)
} }
@ -916,10 +931,16 @@ class MessageComposerViewModel @AssistedInject constructor(
} }
private fun handleStartRecordingVoiceMessage(room: Room) { private fun handleStartRecordingVoiceMessage(room: Room) {
try { val voiceBroadcastState = withState(this) { it.voiceBroadcastState }
audioMessageHelper.startRecording(room.roomId) if (voiceBroadcastState != null && voiceBroadcastState != VoiceBroadcastState.STOPPED) {
} catch (failure: Throwable) { _viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(VoiceFailure.VoiceBroadcastInProgress))
_viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure)) } else {
try {
audioMessageHelper.startRecording(room.roomId)
setState { copy(voiceRecordingUiState = VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis())) }
} catch (failure: Throwable) {
_viewEvents.post(MessageComposerViewEvents.VoicePlaybackOrRecordingFailure(failure))
}
} }
} }

View File

@ -125,7 +125,6 @@ class VoiceRecorderFragment : VectorBaseFragment<FragmentVoiceRecorderBinding>()
if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) {
messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage)
vibrate(requireContext()) vibrate(requireContext())
updateRecordingUiState(VoiceMessageRecorderView.RecordingUiState.Recording(clock.epochMillis()))
} }
} }

View File

@ -19,4 +19,5 @@ package im.vector.app.features.voice
sealed class VoiceFailure(cause: Throwable? = null) : Throwable(cause = cause) { sealed class VoiceFailure(cause: Throwable? = null) : Throwable(cause = cause) {
data class UnableToPlay(val throwable: Throwable) : VoiceFailure(throwable) data class UnableToPlay(val throwable: Throwable) : VoiceFailure(throwable)
data class UnableToRecord(val throwable: Throwable) : VoiceFailure(throwable) data class UnableToRecord(val throwable: Throwable) : VoiceFailure(throwable)
object VoiceBroadcastInProgress : VoiceFailure()
} }