diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 9edd7d836a..b5abefec94 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -3085,6 +3085,10 @@ Play or resume voice broadcast Pause voice broadcast Buffering + Can’t start a new voice broadcast + You don’t have the required permissions to start a voice broadcast in this room. Contact a room administrator to upgrade your permissions. + Someone else is already recording a voice broadcast. Wait for their voice broadcast to end to start a new one. + You are already recording a voice broadcast. Please end your current voice broadcast to start a new one. Anyone in %s will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. Anyone in a parent space will be able to find and join this room - no need to manually invite everyone. You’ll be able to change this in room settings anytime. 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 a09f852958..380c80775b 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 @@ -21,6 +21,8 @@ import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.voice.VoiceFailure +import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure +import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure.RecordingError import org.matrix.android.sdk.api.failure.Failure import org.matrix.android.sdk.api.failure.MatrixError import org.matrix.android.sdk.api.failure.MatrixIdFailure @@ -135,6 +137,7 @@ class DefaultErrorFormatter @Inject constructor( is MatrixIdFailure.InvalidMatrixId -> stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id) is VoiceFailure -> voiceMessageError(throwable) + is VoiceBroadcastFailure -> voiceBroadcastMessageError(throwable) is ActivityNotFoundException -> stringProvider.getString(R.string.error_no_external_application_found) else -> throwable.localizedMessage @@ -149,6 +152,14 @@ class DefaultErrorFormatter @Inject constructor( } } + private fun voiceBroadcastMessageError(throwable: VoiceBroadcastFailure): String { + return when (throwable) { + RecordingError.BlockedBySomeoneElse -> stringProvider.getString(R.string.error_voice_broadcast_blocked_by_someone_else_message) + RecordingError.NoPermission -> stringProvider.getString(R.string.error_voice_broadcast_permission_denied_message) + RecordingError.UserAlreadyBroadcasting -> stringProvider.getString(R.string.error_voice_broadcast_already_in_progress_message) + } + } + private fun limitExceededError(error: MatrixError): String { val delay = error.retryAfterMillis diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 4f51922a62..b259d51947 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -33,6 +33,7 @@ import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import androidx.activity.addCallback +import androidx.annotation.StringRes import androidx.appcompat.view.menu.MenuBuilder import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.ContextCompat @@ -1320,8 +1321,12 @@ class TimelineFragment : } private fun displayRoomDetailActionFailure(result: RoomDetailViewEvents.ActionFailure) { + @StringRes val titleResId = when(result.action) { + RoomDetailAction.VoiceBroadcastAction.Recording.Start -> R.string.error_voice_broadcast_unauthorized_title + else -> R.string.dialog_title_error + } MaterialAlertDialogBuilder(requireActivity()) - .setTitle(R.string.dialog_title_error) + .setTitle(titleResId) .setMessage(errorFormatter.toHumanReadable(result.throwable)) .setPositiveButton(R.string.ok, null) .show() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt index 82ad96d645..ac117558be 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt @@ -604,7 +604,12 @@ class TimelineViewModel @AssistedInject constructor( if (room == null) return viewModelScope.launch { when (action) { - RoomDetailAction.VoiceBroadcastAction.Recording.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId) + RoomDetailAction.VoiceBroadcastAction.Recording.Start -> { + voiceBroadcastHelper.startVoiceBroadcast(room.roomId).fold( + { _viewEvents.post(RoomDetailViewEvents.ActionSuccess(action)) }, + { _viewEvents.post(RoomDetailViewEvents.ActionFailure(action, it)) }, + ) + } RoomDetailAction.VoiceBroadcastAction.Recording.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId) RoomDetailAction.VoiceBroadcastAction.Recording.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId) RoomDetailAction.VoiceBroadcastAction.Recording.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt new file mode 100644 index 0000000000..76b50c78ab --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/VoiceBroadcastFailure.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.voicebroadcast + +sealed class VoiceBroadcastFailure : Throwable() { + sealed class RecordingError : VoiceBroadcastFailure() { + object NoPermission : RecordingError() + object BlockedBySomeoneElse : RecordingError() + object UserAlreadyBroadcasting : RecordingError() + } +} diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt index a1a519a656..f6870f859f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/recording/usecase/StartVoiceBroadcastUseCase.kt @@ -24,15 +24,22 @@ import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.VoiceBroadcastFailure import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.lib.multipicker.utils.toMultiPickerAudioType +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.Room +import org.matrix.android.sdk.api.session.room.getStateEvent +import org.matrix.android.sdk.api.session.room.model.PowerLevelsContent import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper import timber.log.Timber import java.io.File import javax.inject.Inject @@ -50,12 +57,27 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") - val onGoingVoiceBroadcastEvents = getOngoingVoiceBroadcastsUseCase.execute(roomId) + val powerLevelsHelper = room.getStateEvent(EventType.STATE_ROOM_POWER_LEVELS, QueryStringValue.IsEmpty) + ?.content + ?.toModel() + ?.let { PowerLevelsHelper(it) } - if (onGoingVoiceBroadcastEvents.isEmpty()) { - startVoiceBroadcast(room) - } else { - Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: currentVoiceBroadcastEvents=$onGoingVoiceBroadcastEvents") + when { + powerLevelsHelper?.isUserAllowedToSend(session.myUserId, true, VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO) != true -> { + Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: no permission") + throw VoiceBroadcastFailure.RecordingError.NoPermission + } + voiceBroadcastRecorder?.state == VoiceBroadcastRecorder.State.Recording || voiceBroadcastRecorder?.state == VoiceBroadcastRecorder.State.Paused -> { + Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: another voice broadcast") + throw VoiceBroadcastFailure.RecordingError.UserAlreadyBroadcasting + } + getOngoingVoiceBroadcastsUseCase.execute(roomId).isNotEmpty() -> { + Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: user already broadcasting") + throw VoiceBroadcastFailure.RecordingError.BlockedBySomeoneElse + } + else -> { + startVoiceBroadcast(room) + } } }