avoiding stopping any active recording if we're rotating

- had to keep track of the recording start time in order to maintain the current length counter
This commit is contained in:
Adam Brown 2021-11-24 14:23:33 +00:00
parent bbb3a6139f
commit 4a5e21ad21
4 changed files with 37 additions and 27 deletions

View File

@ -699,7 +699,7 @@ class RoomDetailFragment @Inject constructor(
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(RecordingUiState.Started) updateRecordingUiState(RecordingUiState.Started(System.currentTimeMillis()))
} }
} }
@ -713,7 +713,9 @@ class RoomDetailFragment @Inject constructor(
} }
override fun onVoiceRecordingLocked() { 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() { override fun onVoiceRecordingEnded() {
@ -1131,11 +1133,15 @@ class RoomDetailFragment @Inject constructor(
super.onPause() super.onPause()
notificationDrawerManager.setCurrentRoom(null) notificationDrawerManager.setCurrentRoom(null)
voiceMessagePlaybackTracker.unTrack(VoiceMessagePlaybackTracker.RECORDING_ID) 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. if (withState(messageComposerViewModel) { it.isVoiceRecording } && requireActivity().isChangingConfigurations) {
messageComposerViewModel.handle(MessageComposerAction.EndAllVoiceActions(deleteRecord = false)) // we're rotating, maintain any active recordings
views.voiceMessageRecorderView.render(RecordingUiState.None) } 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 { private val attachmentFileActivityResultLauncher = registerStartForActivityResult {

View File

@ -54,8 +54,8 @@ data class MessageComposerViewState(
VoiceMessageRecorderView.RecordingUiState.None, VoiceMessageRecorderView.RecordingUiState.None,
VoiceMessageRecorderView.RecordingUiState.Cancelled, VoiceMessageRecorderView.RecordingUiState.Cancelled,
VoiceMessageRecorderView.RecordingUiState.Playback -> false VoiceMessageRecorderView.RecordingUiState.Playback -> false
VoiceMessageRecorderView.RecordingUiState.Locked, is VoiceMessageRecorderView.RecordingUiState.Locked,
VoiceMessageRecorderView.RecordingUiState.Started -> true is VoiceMessageRecorderView.RecordingUiState.Started -> true
} }
val isVoiceMessageIdle = !isVoiceRecording val isVoiceMessageIdle = !isVoiceRecording

View File

@ -105,32 +105,35 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
fun render(recordingState: RecordingUiState) { fun render(recordingState: RecordingUiState) {
if (lastKnownState == recordingState) return if (lastKnownState == recordingState) return
lastKnownState = recordingState
when (recordingState) { when (recordingState) {
RecordingUiState.None -> { RecordingUiState.None -> {
reset() reset()
} }
RecordingUiState.Started -> { is RecordingUiState.Started -> {
startRecordingTicker() startRecordingTicker(startFromLocked = false, startAt = recordingState.recordingStartTimestamp)
voiceMessageViews.renderToast(context.getString(R.string.voice_message_release_to_send_toast)) voiceMessageViews.renderToast(context.getString(R.string.voice_message_release_to_send_toast))
voiceMessageViews.showRecordingViews() voiceMessageViews.showRecordingViews()
dragState = DraggingState.Ready dragState = DraggingState.Ready
} }
RecordingUiState.Cancelled -> { RecordingUiState.Cancelled -> {
reset() reset()
vibrate(context) vibrate(context)
} }
RecordingUiState.Locked -> { is RecordingUiState.Locked -> {
if (lastKnownState == null) {
startRecordingTicker(startFromLocked = true, startAt = recordingState.recordingStartTimestamp)
}
voiceMessageViews.renderLocked() voiceMessageViews.renderLocked()
postDelayed({ postDelayed({
voiceMessageViews.showRecordingLockedViews(recordingState) voiceMessageViews.showRecordingLockedViews(recordingState)
}, 500) }, 500)
} }
RecordingUiState.Playback -> { RecordingUiState.Playback -> {
stopRecordingTicker() stopRecordingTicker()
voiceMessageViews.showPlaybackViews() voiceMessageViews.showPlaybackViews()
} }
} }
lastKnownState = recordingState
} }
private fun reset() { private fun reset() {
@ -159,22 +162,23 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
dragState = newDragState dragState = newDragState
} }
private fun startRecordingTicker() { private fun startRecordingTicker(startFromLocked: Boolean, startAt: Long) {
val startMs = ((System.currentTimeMillis() - startAt)).coerceAtLeast(0)
recordingTicker?.stop() recordingTicker?.stop()
recordingTicker = CountUpTimer().apply { recordingTicker = CountUpTimer().apply {
tickListener = object : CountUpTimer.TickListener { tickListener = object : CountUpTimer.TickListener {
override fun onTick(milliseconds: Long) { override fun onTick(milliseconds: Long) {
onRecordingTick(milliseconds) val isLocked = startFromLocked || lastKnownState is RecordingUiState.Locked
onRecordingTick(isLocked, milliseconds + startMs)
} }
} }
resume() resume()
} }
onRecordingTick(0L) onRecordingTick(startFromLocked, milliseconds = startMs)
} }
private fun onRecordingTick(milliseconds: Long) { private fun onRecordingTick(isLocked: Boolean, milliseconds: Long) {
val currentState = lastKnownState ?: return voiceMessageViews.renderRecordingTimer(isLocked, milliseconds / 1_000)
voiceMessageViews.renderRecordingTimer(currentState, milliseconds / 1_000)
val timeDiffToRecordingLimit = BuildConfig.VOICE_MESSAGE_DURATION_LIMIT_MS - milliseconds val timeDiffToRecordingLimit = BuildConfig.VOICE_MESSAGE_DURATION_LIMIT_MS - milliseconds
if (timeDiffToRecordingLimit <= 0) { if (timeDiffToRecordingLimit <= 0) {
post { post {
@ -211,9 +215,9 @@ class VoiceMessageRecorderView @JvmOverloads constructor(
sealed interface RecordingUiState { sealed interface RecordingUiState {
object None : RecordingUiState object None : RecordingUiState
object Started : RecordingUiState data class Started(val recordingStartTimestamp: Long) : RecordingUiState
object Cancelled : RecordingUiState object Cancelled : RecordingUiState
object Locked : RecordingUiState data class Locked(val recordingStartTimestamp: Long) : RecordingUiState
object Playback : RecordingUiState object Playback : RecordingUiState
} }

View File

@ -154,7 +154,7 @@ class VoiceMessageViews(
fun hideRecordingViews(recordingState: RecordingUiState) { fun hideRecordingViews(recordingState: RecordingUiState) {
// We need to animate the lock image first // We need to animate the lock image first
if (recordingState != RecordingUiState.Locked) { if (recordingState !is RecordingUiState.Locked) {
views.voiceMessageLockImage.isVisible = false views.voiceMessageLockImage.isVisible = false
views.voiceMessageLockImage.animate().translationY(0f).start() views.voiceMessageLockImage.animate().translationY(0f).start()
views.voiceMessageLockBackground.isVisible = false views.voiceMessageLockBackground.isVisible = false
@ -171,7 +171,7 @@ class VoiceMessageViews(
views.voiceMessageTimerIndicator.isVisible = false views.voiceMessageTimerIndicator.isVisible = false
views.voiceMessageTimer.isVisible = false views.voiceMessageTimer.isVisible = false
if (recordingState != RecordingUiState.Locked) { if (recordingState !is RecordingUiState.Locked) {
views.voiceMessageMicButton views.voiceMessageMicButton
.animate() .animate()
.scaleX(1f) .scaleX(1f)
@ -304,9 +304,9 @@ class VoiceMessageViews(
views.voiceMessageToast.isVisible = false views.voiceMessageToast.isVisible = false
} }
fun renderRecordingTimer(recordingState: RecordingUiState, recordingTimeMillis: Long) { fun renderRecordingTimer(isLocked: Boolean, recordingTimeMillis: Long) {
val formattedTimerText = DateUtils.formatElapsedTime(recordingTimeMillis) val formattedTimerText = DateUtils.formatElapsedTime(recordingTimeMillis)
if (recordingState == RecordingUiState.Locked) { if (isLocked) {
views.voicePlaybackTime.apply { views.voicePlaybackTime.apply {
post { post {
text = formattedTimerText text = formattedTimerText