diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt index 5f96eaf304..8b506e93c4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailFragment.kt @@ -699,7 +699,7 @@ class RoomDetailFragment @Inject constructor( if (checkPermissions(PERMISSIONS_FOR_VOICE_MESSAGE, requireActivity(), permissionVoiceMessageLauncher)) { messageComposerViewModel.handle(MessageComposerAction.StartRecordingVoiceMessage) vibrate(requireContext()) - updateRecordingUiState(RecordingUiState.Started) + updateRecordingUiState(RecordingUiState.Started(System.currentTimeMillis())) } } @@ -713,7 +713,9 @@ class RoomDetailFragment @Inject constructor( } override fun onVoiceRecordingLocked() { - updateRecordingUiState(RecordingUiState.Locked) + val startedState = withState(messageComposerViewModel) { it.voiceRecordingUiState as? RecordingUiState.Started } + val startTime = startedState?.recordingStartTimestamp ?: System.currentTimeMillis() + updateRecordingUiState(RecordingUiState.Locked(startTime)) } override fun onVoiceRecordingEnded() { @@ -1131,11 +1133,15 @@ class RoomDetailFragment @Inject constructor( super.onPause() notificationDrawerManager.setCurrentRoom(null) voiceMessagePlaybackTracker.unTrack(VoiceMessagePlaybackTracker.RECORDING_ID) - messageComposerViewModel.handle(MessageComposerAction.SaveDraft(views.composerLayout.text.toString())) - // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. - messageComposerViewModel.handle(MessageComposerAction.EndAllVoiceActions(deleteRecord = false)) - views.voiceMessageRecorderView.render(RecordingUiState.None) + if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) { + // we're rotating, maintain any active recordings + } else { + messageComposerViewModel.handle(MessageComposerAction.SaveDraft(views.composerLayout.text.toString())) + // We should improve the UX to support going into playback mode when paused and delete the media when the view is destroyed. + messageComposerViewModel.handle(MessageComposerAction.EndAllVoiceActions(deleteRecord = false)) + views.voiceMessageRecorderView.render(RecordingUiState.None) + } } private val attachmentFileActivityResultLauncher = registerStartForActivityResult { diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt index 0df093c661..f9c32d3194 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewState.kt @@ -54,8 +54,8 @@ data class MessageComposerViewState( VoiceMessageRecorderView.RecordingUiState.None, VoiceMessageRecorderView.RecordingUiState.Cancelled, VoiceMessageRecorderView.RecordingUiState.Playback -> false - VoiceMessageRecorderView.RecordingUiState.Locked, - VoiceMessageRecorderView.RecordingUiState.Started -> true + is VoiceMessageRecorderView.RecordingUiState.Locked, + is VoiceMessageRecorderView.RecordingUiState.Started -> true } val isVoiceMessageIdle = !isVoiceRecording diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt index 2923af140d..3b4c8a58c4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageRecorderView.kt @@ -105,32 +105,35 @@ class VoiceMessageRecorderView @JvmOverloads constructor( fun render(recordingState: RecordingUiState) { if (lastKnownState == recordingState) return - lastKnownState = recordingState when (recordingState) { - RecordingUiState.None -> { + RecordingUiState.None -> { reset() } - RecordingUiState.Started -> { - startRecordingTicker() + is RecordingUiState.Started -> { + startRecordingTicker(startFromLocked = false, startAt = recordingState.recordingStartTimestamp) voiceMessageViews.renderToast(context.getString(R.string.voice_message_release_to_send_toast)) voiceMessageViews.showRecordingViews() dragState = DraggingState.Ready } - RecordingUiState.Cancelled -> { + RecordingUiState.Cancelled -> { reset() vibrate(context) } - RecordingUiState.Locked -> { + is RecordingUiState.Locked -> { + if (lastKnownState == null) { + startRecordingTicker(startFromLocked = true, startAt = recordingState.recordingStartTimestamp) + } voiceMessageViews.renderLocked() postDelayed({ voiceMessageViews.showRecordingLockedViews(recordingState) }, 500) } - RecordingUiState.Playback -> { + RecordingUiState.Playback -> { stopRecordingTicker() voiceMessageViews.showPlaybackViews() } } + lastKnownState = recordingState } private fun reset() { @@ -159,22 +162,23 @@ class VoiceMessageRecorderView @JvmOverloads constructor( dragState = newDragState } - private fun startRecordingTicker() { + private fun startRecordingTicker(startFromLocked: Boolean, startAt: Long) { + val startMs = ((System.currentTimeMillis() - startAt)).coerceAtLeast(0) recordingTicker?.stop() recordingTicker = CountUpTimer().apply { tickListener = object : CountUpTimer.TickListener { override fun onTick(milliseconds: Long) { - onRecordingTick(milliseconds) + val isLocked = startFromLocked || lastKnownState is RecordingUiState.Locked + onRecordingTick(isLocked, milliseconds + startMs) } } resume() } - onRecordingTick(0L) + onRecordingTick(startFromLocked, milliseconds = startMs) } - private fun onRecordingTick(milliseconds: Long) { - val currentState = lastKnownState ?: return - voiceMessageViews.renderRecordingTimer(currentState, milliseconds / 1_000) + private fun onRecordingTick(isLocked: Boolean, milliseconds: Long) { + voiceMessageViews.renderRecordingTimer(isLocked, milliseconds / 1_000) val timeDiffToRecordingLimit = BuildConfig.VOICE_MESSAGE_DURATION_LIMIT_MS - milliseconds if (timeDiffToRecordingLimit <= 0) { post { @@ -211,9 +215,9 @@ class VoiceMessageRecorderView @JvmOverloads constructor( sealed interface RecordingUiState { object None : RecordingUiState - object Started : RecordingUiState + data class Started(val recordingStartTimestamp: Long) : RecordingUiState object Cancelled : RecordingUiState - object Locked : RecordingUiState + data class Locked(val recordingStartTimestamp: Long) : RecordingUiState object Playback : RecordingUiState } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt index 32f21a3177..e138e14261 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/composer/voice/VoiceMessageViews.kt @@ -154,7 +154,7 @@ class VoiceMessageViews( fun hideRecordingViews(recordingState: RecordingUiState) { // We need to animate the lock image first - if (recordingState != RecordingUiState.Locked) { + if (recordingState !is RecordingUiState.Locked) { views.voiceMessageLockImage.isVisible = false views.voiceMessageLockImage.animate().translationY(0f).start() views.voiceMessageLockBackground.isVisible = false @@ -171,7 +171,7 @@ class VoiceMessageViews( views.voiceMessageTimerIndicator.isVisible = false views.voiceMessageTimer.isVisible = false - if (recordingState != RecordingUiState.Locked) { + if (recordingState !is RecordingUiState.Locked) { views.voiceMessageMicButton .animate() .scaleX(1f) @@ -304,9 +304,9 @@ class VoiceMessageViews( views.voiceMessageToast.isVisible = false } - fun renderRecordingTimer(recordingState: RecordingUiState, recordingTimeMillis: Long) { + fun renderRecordingTimer(isLocked: Boolean, recordingTimeMillis: Long) { val formattedTimerText = DateUtils.formatElapsedTime(recordingTimeMillis) - if (recordingState == RecordingUiState.Locked) { + if (isLocked) { views.voicePlaybackTime.apply { post { text = formattedTimerText