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 <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2023-02-22 12:08:17 +01:00
parent c1b79ac4dd
commit 499e022114
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
8 changed files with 89 additions and 41 deletions

View File

@ -445,12 +445,21 @@ public class CallActivity extends CallBaseActivity {
callRecordingViewModel.getViewState().observe(this, viewState -> { callRecordingViewModel.getViewState().observe(this, viewState -> {
if (viewState instanceof CallRecordingViewModel.RecordingStartedState) { if (viewState instanceof CallRecordingViewModel.RecordingStartedState) {
binding.callRecordingIndicator.setImageResource(R.drawable.record_stop);
binding.callRecordingIndicator.setVisibility(View.VISIBLE); binding.callRecordingIndicator.setVisibility(View.VISIBLE);
if (((CallRecordingViewModel.RecordingStartedState) viewState).getShowStartedInfo()) { if (((CallRecordingViewModel.RecordingStartedState) viewState).getShowStartedInfo()) {
VibrationUtils.INSTANCE.vibrateShort(context); VibrationUtils.INSTANCE.vibrateShort(context);
Toast.makeText(context, context.getResources().getString(R.string.record_active_info), Toast.LENGTH_LONG).show(); 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) { } else if (viewState instanceof CallRecordingViewModel.RecordingConfirmStopState) {
if (isAllowedToStartOrStopRecording()) {
MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.record_stop_confirm_title) .setTitle(R.string.record_stop_confirm_title)
.setMessage(R.string.record_stop_confirm_message) .setMessage(R.string.record_stop_confirm_message)
@ -465,10 +474,15 @@ public class CallActivity extends CallBaseActivity {
dialog.getButton(AlertDialog.BUTTON_POSITIVE), dialog.getButton(AlertDialog.BUTTON_POSITIVE),
dialog.getButton(AlertDialog.BUTTON_NEGATIVE) 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) { } else if (viewState instanceof CallRecordingViewModel.RecordingErrorState) {
Toast.makeText(context, context.getResources().getString(R.string.nc_common_error_sorry), if (isAllowedToStartOrStopRecording()) {
Toast.makeText(context, context.getResources().getString(R.string.record_failed_info),
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
}
binding.callRecordingIndicator.setVisibility(View.GONE);
} else { } else {
binding.callRecordingIndicator.setVisibility(View.GONE); binding.callRecordingIndicator.setVisibility(View.GONE);
} }
@ -603,7 +617,14 @@ public class CallActivity extends CallBaseActivity {
binding.callRecordingIndicator.setOnClickListener(l -> { binding.callRecordingIndicator.setOnClickListener(l -> {
if (isAllowedToStartOrStopRecording()) { if (isAllowedToStartOrStopRecording()) {
if (callRecordingViewModel.getViewState().getValue() instanceof CallRecordingViewModel.RecordingStartingState) {
if (moreCallActionsDialog == null) {
moreCallActionsDialog = new MoreCallActionsDialog(this);
}
moreCallActionsDialog.show();
} else {
callRecordingViewModel.clickRecordButton(); callRecordingViewModel.clickRecordButton();
}
} else { } else {
Toast.makeText(context, context.getResources().getString(R.string.record_active_info), Toast.LENGTH_LONG).show(); Toast.makeText(context, context.getResources().getString(R.string.record_active_info), Toast.LENGTH_LONG).show();
} }

View File

@ -530,6 +530,7 @@ data class ChatMessage(
RECORDING_STOPPED, RECORDING_STOPPED,
AUDIO_RECORDING_STARTED, AUDIO_RECORDING_STARTED,
AUDIO_RECORDING_STOPPED, AUDIO_RECORDING_STOPPED,
RECORDING_FAILED,
BREAKOUT_ROOMS_STARTED, BREAKOUT_ROOMS_STARTED,
BREAKOUT_ROOMS_STOPPED BREAKOUT_ROOMS_STOPPED
} }

View File

@ -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.REACTION_REVOKED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.READ_ONLY 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.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_STARTED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.RECORDING_STOPPED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.RECORDING_STOPPED
import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_ADDED import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType.USER_ADDED
@ -143,6 +144,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
"recording_stopped" -> RECORDING_STOPPED "recording_stopped" -> RECORDING_STOPPED
"audio_recording_started" -> AUDIO_RECORDING_STARTED "audio_recording_started" -> AUDIO_RECORDING_STARTED
"audio_recording_stopped" -> AUDIO_RECORDING_STOPPED "audio_recording_stopped" -> AUDIO_RECORDING_STOPPED
"recording_failed" -> RECORDING_FAILED
"breakout_rooms_started" -> BREAKOUT_ROOMS_STARTED "breakout_rooms_started" -> BREAKOUT_ROOMS_STARTED
"breakout_rooms_stopped" -> BREAKOUT_ROOMS_STOPPED "breakout_rooms_stopped" -> BREAKOUT_ROOMS_STOPPED
else -> DUMMY else -> DUMMY
@ -206,6 +208,7 @@ class EnumSystemMessageTypeConverter : StringBasedTypeConverter<ChatMessage.Syst
RECORDING_STOPPED -> "recording_stopped" RECORDING_STOPPED -> "recording_stopped"
AUDIO_RECORDING_STARTED -> "audio_recording_started" AUDIO_RECORDING_STARTED -> "audio_recording_started"
AUDIO_RECORDING_STOPPED -> "audio_recording_stopped" AUDIO_RECORDING_STOPPED -> "audio_recording_stopped"
RECORDING_FAILED -> "recording_failed"
BREAKOUT_ROOMS_STARTED -> "breakout_rooms_started" BREAKOUT_ROOMS_STARTED -> "breakout_rooms_started"
BREAKOUT_ROOMS_STOPPED -> "breakout_rooms_stopped" BREAKOUT_ROOMS_STOPPED -> "breakout_rooms_stopped"
else -> "" else -> ""

View File

@ -94,6 +94,20 @@ class MoreCallActionsDialog(private val callActivity: CallActivity) : BottomShee
private fun initObservers() { private fun initObservers() {
callActivity.callRecordingViewModel.viewState.observe(this) { state -> callActivity.callRecordingViewModel.viewState.observe(this) { state ->
when (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 -> { is CallRecordingViewModel.RecordingStartedState -> {
binding.recordCallText.text = context.getText(R.string.record_stop_description) binding.recordCallText.text = context.getText(R.string.record_stop_description)
binding.recordCallIcon.setImageDrawable( binding.recordCallIcon.setImageDrawable(
@ -101,18 +115,8 @@ class MoreCallActionsDialog(private val callActivity: CallActivity) : BottomShee
) )
dismiss() dismiss()
} }
is CallRecordingViewModel.RecordingStoppedState -> { is CallRecordingViewModel.RecordingStoppingState -> {
binding.recordCallText.text = context.getText(R.string.record_start_description) binding.recordCallText.text = context.getText(R.string.record_stopping)
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.RecordingConfirmStopState -> { is CallRecordingViewModel.RecordingConfirmStopState -> {
binding.recordCallText.text = context.getText(R.string.record_stop_description) binding.recordCallText.text = context.getText(R.string.record_stop_description)

View File

@ -42,11 +42,11 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec
lateinit var roomToken: String lateinit var roomToken: String
sealed interface ViewState sealed interface ViewState
open class RecordingStartedState(val showStartedInfo: Boolean) : ViewState open class RecordingStartedState(val hasVideo: Boolean, val showStartedInfo: Boolean) : ViewState
object RecordingStoppedState : ViewState object RecordingStoppedState : ViewState
object RecordingStartLoadingState : ViewState open class RecordingStartingState(val hasVideo: Boolean) : ViewState
object RecordingStopLoadingState : ViewState object RecordingStoppingState : ViewState
object RecordingConfirmStopState : ViewState object RecordingConfirmStopState : ViewState
object RecordingErrorState : ViewState object RecordingErrorState : ViewState
@ -69,6 +69,9 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec
// just show it again. // just show it again.
_viewState.value = RecordingConfirmStopState _viewState.value = RecordingConfirmStopState
} }
is RecordingStartingState -> {
stopRecording()
}
RecordingErrorState -> { RecordingErrorState -> {
stopRecording() stopRecording()
} }
@ -77,7 +80,7 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec
} }
private fun startRecording() { private fun startRecording() {
_viewState.value = RecordingStartLoadingState _viewState.value = RecordingStartingState(true)
repository.startRecording(roomToken) repository.startRecording(roomToken)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread()) ?.observeOn(AndroidSchedulers.mainThread())
@ -85,7 +88,7 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec
} }
fun stopRecording() { fun stopRecording() {
_viewState.value = RecordingStopLoadingState _viewState.value = RecordingStoppingState
repository.stopRecording(roomToken) repository.stopRecording(roomToken)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread()) ?.observeOn(AndroidSchedulers.mainThread())
@ -93,7 +96,7 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec
} }
fun dismissStopRecording() { fun dismissStopRecording() {
_viewState.value = RecordingStartedState(false) _viewState.value = RecordingStartedState(true, false)
} }
override fun onCleared() { override fun onCleared() {
@ -109,8 +112,11 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec
fun setRecordingState(state: Int) { fun setRecordingState(state: Int) {
when (state) { when (state) {
RECORDING_STOPPED_CODE -> _viewState.value = RecordingStoppedState RECORDING_STOPPED_CODE -> _viewState.value = RecordingStoppedState
RECORDING_STARTED_VIDEO_CODE -> _viewState.value = RecordingStartedState(true) RECORDING_STARTED_VIDEO_CODE -> _viewState.value = RecordingStartedState(true, true)
RECORDING_STARTED_AUDIO_CODE -> _viewState.value = RecordingStartedState(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 -> {} else -> {}
} }
} }
@ -160,5 +166,8 @@ class CallRecordingViewModel @Inject constructor(private val repository: CallRec
const val RECORDING_STOPPED_CODE = 0 const val RECORDING_STOPPED_CODE = 0
const val RECORDING_STARTED_VIDEO_CODE = 1 const val RECORDING_STARTED_VIDEO_CODE = 1
const val RECORDING_STARTED_AUDIO_CODE = 2 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
} }
} }

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/grey_600"
android:pathData="M12,2A10,10 0,0 0,2 12A10,10 0,0 0,12 22A10,10 0,0 0,22 12A10,10 0,0 0,12 2M9,9H15V15H9"/>
</vector>

View File

@ -582,12 +582,13 @@ How to translate with transifex:
<!-- Call recording --> <!-- Call recording -->
<string name="record_start_description">Start recording</string> <string name="record_start_description">Start recording</string>
<string name="record_start_loading">Starting …</string> <string name="record_starting">Cancel recording start</string>
<string name="record_stop_description">Stop recording</string> <string name="record_stop_description">Stop recording</string>
<string name="record_stop_loading">Stopping …</string> <string name="record_stopping">Stopping recording …</string>
<string name="record_stop_confirm_title">Stop Call recording</string> <string name="record_stop_confirm_title">Stop Call recording</string>
<string name="record_stop_confirm_message">Do you really want to stop the recording?</string> <string name="record_stop_confirm_message">Do you really want to stop the recording?</string>
<string name="record_active_info">The call is being recorded</string> <string name="record_active_info">The call is being recorded</string>
<string name="record_failed_info">The recording failed. Please contact your administrator.</string>
<!-- Shared items --> <!-- Shared items -->
<string name="shared_items_media">Media</string> <string name="shared_items_media">Media</string>

View File

@ -21,7 +21,7 @@ class CallRecordingViewModelTest : AbstractViewModelTest() {
viewModel.setData("foo") viewModel.setData("foo")
viewModel.clickRecordButton() 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 // fake to execute setRecordingState which would be triggered by signaling message
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_VIDEO_CODE) viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_VIDEO_CODE)
@ -78,7 +78,7 @@ class CallRecordingViewModelTest : AbstractViewModelTest() {
viewModel.dismissStopRecording() viewModel.dismissStopRecording()
Assert.equals( Assert.equals(
CallRecordingViewModel.RecordingStartedState(false).javaClass, CallRecordingViewModel.RecordingStartedState(true, false).javaClass,
viewModel.viewState.value?.javaClass viewModel.viewState.value?.javaClass
) )
Assert.equals( Assert.equals(