From 499e022114909c94d8a4ff0ac69799df3edb6ec4 Mon Sep 17 00:00:00 2001 From: Marcel Hibbe Date: Wed, 22 Feb 2023 12:08:17 +0100 Subject: [PATCH] Handle more call recording states more call recording states are: 3 = Starting video recording 4 = Starting audio recording 5 = Recording failed these actions were added: - Show grey recording icon to moderators if recording is starting - Show toast to moderators if recording failed - Add system message for recording failed Signed-off-by: Marcel Hibbe --- .../talk/activities/CallActivity.java | 55 +++++++++++++------ .../talk/models/json/chat/ChatMessage.kt | 1 + .../EnumSystemMessageTypeConverter.kt | 3 + .../talk/ui/dialog/MoreCallActionsDialog.kt | 28 ++++++---- .../talk/viewmodels/CallRecordingViewModel.kt | 25 ++++++--- app/src/main/res/drawable/record_starting.xml | 9 +++ app/src/main/res/values/strings.xml | 5 +- .../viewmodels/CallRecordingViewModelTest.kt | 4 +- 8 files changed, 89 insertions(+), 41 deletions(-) create mode 100644 app/src/main/res/drawable/record_starting.xml diff --git a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java index cfc286f84..fb4fc34c1 100644 --- a/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java +++ b/app/src/main/java/com/nextcloud/talk/activities/CallActivity.java @@ -445,30 +445,44 @@ public class CallActivity extends CallBaseActivity { callRecordingViewModel.getViewState().observe(this, viewState -> { if (viewState instanceof CallRecordingViewModel.RecordingStartedState) { + binding.callRecordingIndicator.setImageResource(R.drawable.record_stop); binding.callRecordingIndicator.setVisibility(View.VISIBLE); if (((CallRecordingViewModel.RecordingStartedState) viewState).getShowStartedInfo()) { VibrationUtils.INSTANCE.vibrateShort(context); Toast.makeText(context, context.getResources().getString(R.string.record_active_info), Toast.LENGTH_LONG).show(); } + } else if (viewState instanceof CallRecordingViewModel.RecordingStartingState) { + if (isAllowedToStartOrStopRecording()) { + binding.callRecordingIndicator.setImageResource(R.drawable.record_starting); + binding.callRecordingIndicator.setVisibility(View.VISIBLE); + } else { + binding.callRecordingIndicator.setVisibility(View.GONE); + } } else if (viewState instanceof CallRecordingViewModel.RecordingConfirmStopState) { - MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(this) - .setTitle(R.string.record_stop_confirm_title) - .setMessage(R.string.record_stop_confirm_message) - .setPositiveButton(R.string.record_stop_description, - (dialog, which) -> callRecordingViewModel.stopRecording()) - .setNegativeButton(R.string.nc_common_dismiss, - (dialog, which) -> callRecordingViewModel.dismissStopRecording()); - viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder); - AlertDialog dialog = dialogBuilder.show(); - - viewThemeUtils.platform.colorTextButtons( - dialog.getButton(AlertDialog.BUTTON_POSITIVE), - dialog.getButton(AlertDialog.BUTTON_NEGATIVE) - ); + if (isAllowedToStartOrStopRecording()) { + MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(this) + .setTitle(R.string.record_stop_confirm_title) + .setMessage(R.string.record_stop_confirm_message) + .setPositiveButton(R.string.record_stop_description, + (dialog, which) -> callRecordingViewModel.stopRecording()) + .setNegativeButton(R.string.nc_common_dismiss, + (dialog, which) -> callRecordingViewModel.dismissStopRecording()); + viewThemeUtils.dialog.colorMaterialAlertDialogBackground(this, dialogBuilder); + AlertDialog dialog = dialogBuilder.show(); + viewThemeUtils.platform.colorTextButtons( + dialog.getButton(AlertDialog.BUTTON_POSITIVE), + dialog.getButton(AlertDialog.BUTTON_NEGATIVE) + ); + } else { + Log.e(TAG, "Being in RecordingConfirmStopState as non moderator. This should not happen!"); + } } else if (viewState instanceof CallRecordingViewModel.RecordingErrorState) { - Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry), - Toast.LENGTH_LONG).show(); + if (isAllowedToStartOrStopRecording()) { + Toast.makeText(context, context.getResources().getString(R.string.record_failed_info), + Toast.LENGTH_LONG).show(); + } + binding.callRecordingIndicator.setVisibility(View.GONE); } else { binding.callRecordingIndicator.setVisibility(View.GONE); } @@ -603,7 +617,14 @@ public class CallActivity extends CallBaseActivity { binding.callRecordingIndicator.setOnClickListener(l -> { if (isAllowedToStartOrStopRecording()) { - callRecordingViewModel.clickRecordButton(); + if (callRecordingViewModel.getViewState().getValue() instanceof CallRecordingViewModel.RecordingStartingState) { + if (moreCallActionsDialog == null) { + moreCallActionsDialog = new MoreCallActionsDialog(this); + } + moreCallActionsDialog.show(); + } else { + callRecordingViewModel.clickRecordButton(); + } } else { Toast.makeText(context, context.getResources().getString(R.string.record_active_info), Toast.LENGTH_LONG).show(); } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt index e60e3314d..79f99f5b0 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.kt @@ -530,6 +530,7 @@ data class ChatMessage( RECORDING_STOPPED, AUDIO_RECORDING_STARTED, AUDIO_RECORDING_STOPPED, + RECORDING_FAILED, BREAKOUT_ROOMS_STARTED, BREAKOUT_ROOMS_STOPPED } diff --git a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt index dded74d64..0722d1b53 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/converters/EnumSystemMessageTypeConverter.kt @@ -78,6 +78,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTIO import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.REACTION_REVOKED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY_OFF +import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.RECORDING_FAILED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.RECORDING_STARTED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.RECORDING_STOPPED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_ADDED @@ -143,6 +144,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter RECORDING_STOPPED "audio_recording_started" -> AUDIO_RECORDING_STARTED "audio_recording_stopped" -> AUDIO_RECORDING_STOPPED + "recording_failed" -> RECORDING_FAILED "breakout_rooms_started" -> BREAKOUT_ROOMS_STARTED "breakout_rooms_stopped" -> BREAKOUT_ROOMS_STOPPED else -> DUMMY @@ -206,6 +208,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter "recording_stopped" AUDIO_RECORDING_STARTED -> "audio_recording_started" AUDIO_RECORDING_STOPPED -> "audio_recording_stopped" + RECORDING_FAILED -> "recording_failed" BREAKOUT_ROOMS_STARTED -> "breakout_rooms_started" BREAKOUT_ROOMS_STOPPED -> "breakout_rooms_stopped" else -> "" diff --git a/app/src/main/java/com/nextcloud/talk/ui/dialog/MoreCallActionsDialog.kt b/app/src/main/java/com/nextcloud/talk/ui/dialog/MoreCallActionsDialog.kt index 717fec352..7208978c6 100644 --- a/app/src/main/java/com/nextcloud/talk/ui/dialog/MoreCallActionsDialog.kt +++ b/app/src/main/java/com/nextcloud/talk/ui/dialog/MoreCallActionsDialog.kt @@ -94,6 +94,20 @@ class MoreCallActionsDialog(private val callActivity: CallActivity) : BottomShee private fun initObservers() { callActivity.callRecordingViewModel.viewState.observe(this) { state -> when (state) { + is CallRecordingViewModel.RecordingStoppedState, + is CallRecordingViewModel.RecordingErrorState -> { + binding.recordCallText.text = context.getText(R.string.record_start_description) + binding.recordCallIcon.setImageDrawable( + ContextCompat.getDrawable(context, R.drawable.record_start) + ) + dismiss() + } + is CallRecordingViewModel.RecordingStartingState -> { + binding.recordCallText.text = context.getText(R.string.record_starting) + binding.recordCallIcon.setImageDrawable( + ContextCompat.getDrawable(context, R.drawable.record_stop) + ) + } is CallRecordingViewModel.RecordingStartedState -> { binding.recordCallText.text = context.getText(R.string.record_stop_description) binding.recordCallIcon.setImageDrawable( @@ -101,18 +115,8 @@ class MoreCallActionsDialog(private val callActivity: CallActivity) : BottomShee ) dismiss() } - is CallRecordingViewModel.RecordingStoppedState -> { - binding.recordCallText.text = context.getText(R.string.record_start_description) - binding.recordCallIcon.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.record_start) - ) - dismiss() - } - is CallRecordingViewModel.RecordingStartLoadingState -> { - binding.recordCallText.text = context.getText(R.string.record_start_loading) - } - is CallRecordingViewModel.RecordingStopLoadingState -> { - binding.recordCallText.text = context.getText(R.string.record_stop_loading) + is CallRecordingViewModel.RecordingStoppingState -> { + binding.recordCallText.text = context.getText(R.string.record_stopping) } is CallRecordingViewModel.RecordingConfirmStopState -> { binding.recordCallText.text = context.getText(R.string.record_stop_description) diff --git a/app/src/main/java/com/nextcloud/talk/viewmodels/CallRecordingViewModel.kt b/app/src/main/java/com/nextcloud/talk/viewmodels/CallRecordingViewModel.kt index 2cdc62a7c..3cff3d02b 100644 --- a/app/src/main/java/com/nextcloud/talk/viewmodels/CallRecordingViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/viewmodels/CallRecordingViewModel.kt @@ -42,11 +42,11 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec lateinit var roomToken: String sealed interface ViewState - open class RecordingStartedState(val showStartedInfo: Boolean) : ViewState + open class RecordingStartedState(val hasVideo: Boolean, val showStartedInfo: Boolean) : ViewState object RecordingStoppedState : ViewState - object RecordingStartLoadingState : ViewState - object RecordingStopLoadingState : ViewState + open class RecordingStartingState(val hasVideo: Boolean) : ViewState + object RecordingStoppingState : ViewState object RecordingConfirmStopState : ViewState object RecordingErrorState : ViewState @@ -69,6 +69,9 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec // just show it again. _viewState.value = RecordingConfirmStopState } + is RecordingStartingState -> { + stopRecording() + } RecordingErrorState -> { stopRecording() } @@ -77,7 +80,7 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec } private fun startRecording() { - _viewState.value = RecordingStartLoadingState + _viewState.value = RecordingStartingState(true) repository.startRecording(roomToken) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) @@ -85,7 +88,7 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec } fun stopRecording() { - _viewState.value = RecordingStopLoadingState + _viewState.value = RecordingStoppingState repository.stopRecording(roomToken) .subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) @@ -93,7 +96,7 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec } fun dismissStopRecording() { - _viewState.value = RecordingStartedState(false) + _viewState.value = RecordingStartedState(true, false) } override fun onCleared() { @@ -109,8 +112,11 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec fun setRecordingState(state: Int) { when (state) { RECORDING_STOPPED_CODE -> _viewState.value = RecordingStoppedState - RECORDING_STARTED_VIDEO_CODE -> _viewState.value = RecordingStartedState(true) - RECORDING_STARTED_AUDIO_CODE -> _viewState.value = RecordingStartedState(true) + RECORDING_STARTED_VIDEO_CODE -> _viewState.value = RecordingStartedState(true, true) + RECORDING_STARTED_AUDIO_CODE -> _viewState.value = RecordingStartedState(false, true) + RECORDING_STARTING_VIDEO_CODE -> _viewState.value = RecordingStartingState(true) + RECORDING_STARTING_AUDIO_CODE -> _viewState.value = RecordingStartingState(false) + RECORDING_FAILED_CODE -> _viewState.value = RecordingErrorState else -> {} } } @@ -160,5 +166,8 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec const val RECORDING_STOPPED_CODE = 0 const val RECORDING_STARTED_VIDEO_CODE = 1 const val RECORDING_STARTED_AUDIO_CODE = 2 + const val RECORDING_STARTING_VIDEO_CODE = 3 + const val RECORDING_STARTING_AUDIO_CODE = 4 + const val RECORDING_FAILED_CODE = 5 } } diff --git a/app/src/main/res/drawable/record_starting.xml b/app/src/main/res/drawable/record_starting.xml new file mode 100644 index 000000000..216ebb4ae --- /dev/null +++ b/app/src/main/res/drawable/record_starting.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dde01f787..6acaf4348 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -582,12 +582,13 @@ How to translate with transifex: Start recording - Starting … + Cancel recording start Stop recording - Stopping … + Stopping recording … Stop Call recording Do you really want to stop the recording? The call is being recorded + The recording failed. Please contact your administrator. Media diff --git a/app/src/test/java/com/nextcloud/talk/viewmodels/CallRecordingViewModelTest.kt b/app/src/test/java/com/nextcloud/talk/viewmodels/CallRecordingViewModelTest.kt index bad4de56d..fc44e74f1 100644 --- a/app/src/test/java/com/nextcloud/talk/viewmodels/CallRecordingViewModelTest.kt +++ b/app/src/test/java/com/nextcloud/talk/viewmodels/CallRecordingViewModelTest.kt @@ -21,7 +21,7 @@ class CallRecordingViewModelTest : AbstractViewModelTest() { viewModel.setData("foo") viewModel.clickRecordButton() - Assert.equals(CallRecordingViewModel.RecordingStartLoadingState, viewModel.viewState.value) + Assert.equals(CallRecordingViewModel.RecordingStartingState(true), viewModel.viewState.value) // fake to execute setRecordingState which would be triggered by signaling message viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_VIDEO_CODE) @@ -78,7 +78,7 @@ class CallRecordingViewModelTest : AbstractViewModelTest() { viewModel.dismissStopRecording() Assert.equals( - CallRecordingViewModel.RecordingStartedState(false).javaClass, + CallRecordingViewModel.RecordingStartedState(true, false).javaClass, viewModel.viewState.value?.javaClass ) Assert.equals(