Display duration of recorded voice message (#1733)

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
jonnyandrew 2023-11-03 12:59:36 +00:00 committed by GitHub
parent 14dc8e1059
commit ddc1e1d0cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 119 additions and 40 deletions

View file

@ -36,7 +36,7 @@ class VoiceMessageComposerPlayer @Inject constructor(
val state: Flow<State> = mediaPlayer.state.map { state ->
if (lastPlayedMediaPath == null || lastPlayedMediaPath != state.mediaId) {
return@map State.NotPlaying
return@map State.NotLoaded
}
State(
@ -89,13 +89,15 @@ class VoiceMessageComposerPlayer @Inject constructor(
val duration: Long,
) {
companion object {
val NotPlaying = State(
val NotLoaded = State(
isPlaying = false,
currentPosition = 0L,
duration = 0L,
)
}
val isLoaded get() = this != NotLoaded
/**
* The progress of this player between 0 and 1.
*/

View file

@ -42,14 +42,15 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.voicerecorder.api.VoiceRecorder
import io.element.android.libraries.voicerecorder.api.VoiceRecorderState
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import javax.inject.Inject
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@SingleIn(RoomScope::class)
@ -72,8 +73,8 @@ class VoiceMessageComposerPresenter @Inject constructor(
val permissionState = permissionsPresenter.present()
var isSending by remember { mutableStateOf(false) }
val playerState by player.state.collectAsState(initial = VoiceMessageComposerPlayer.State.NotPlaying)
val isPlaying by remember(playerState.isPlaying) { derivedStateOf { playerState.isPlaying } }
val playerState by player.state.collectAsState(initial = VoiceMessageComposerPlayer.State.NotLoaded)
val playerTime by remember(playerState, recorderState) { derivedStateOf { displayTime(playerState, recorderState) } }
val waveform by remember(recorderState) { derivedStateOf { recorderState.finishedWaveform() } }
val onLifecycleEvent = { event: Lifecycle.Event ->
@ -190,9 +191,10 @@ class VoiceMessageComposerPresenter @Inject constructor(
)
is VoiceRecorderState.Finished -> VoiceMessageState.Preview(
isSending = isSending,
isPlaying = isPlaying,
isPlaying = playerState.isPlaying,
showCursor = playerState.isLoaded && !isSending,
playbackProgress = playerState.progress,
time = playerState.currentPosition.milliseconds,
time = playerTime,
waveform = waveform,
)
else -> VoiceMessageState.Idle
@ -259,3 +261,20 @@ private fun VoiceRecorderState.finishedWaveform(): ImmutableList<Float> =
?.waveform
.orEmpty()
.toImmutableList()
/**
* The time to display depending on the player state.
*
* Either the current position or total duration.
*/
private fun displayTime(
playerState: VoiceMessageComposerPlayer.State,
recording: VoiceRecorderState
): Duration = when {
playerState.isLoaded ->
playerState.currentPosition.milliseconds
recording is VoiceRecorderState.Finished ->
recording.duration
else ->
0.milliseconds
}

View file

@ -179,7 +179,7 @@ class VoiceMessageComposerPresenterTest {
}
// Nothing should happen
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Recording(RECORDING_DURATION, RECORDING_STATE.levels))
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
voiceRecorder.assertCalls(started = 1, stopped = 0, deleted = 0)
testPauseAndDestroy(finalState)
@ -196,7 +196,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
val finalState = awaitItem().also {
assertThat(it.voiceMessageState).isEqualTo(aPreviewState(isPlaying = true, playbackProgress = 0.1f, time = RECORDING_DURATION))
assertThat(it.voiceMessageState).isEqualTo(aPlayingState())
}
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)
@ -215,7 +215,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Pause))
val finalState = awaitItem().also {
assertThat(it.voiceMessageState).isEqualTo(aPreviewState(isPlaying = false, playbackProgress = 0.1f, time = RECORDING_DURATION))
assertThat(it.voiceMessageState).isEqualTo(aPausedState())
}
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)
@ -252,7 +252,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage)
awaitItem().apply {
assertThat(voiceMessageState).isEqualTo(aPreviewState(isPlaying = false, playbackProgress = 0.1f, time = RECORDING_DURATION))
assertThat(voiceMessageState).isEqualTo(aPausedState())
}
val finalState = awaitItem()
@ -272,7 +272,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState(isSending = true))
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState())
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
@ -322,11 +322,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(
aPreviewState(
isSending = true, isPlaying = false, playbackProgress = 0.1f, time = RECORDING_DURATION
)
)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPlayingState().toSendingState())
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
@ -349,7 +345,7 @@ class VoiceMessageComposerPresenterTest {
eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
}
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState(isSending = true))
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState())
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
@ -398,7 +394,7 @@ class VoiceMessageComposerPresenterTest {
val previewState = awaitItem()
previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState(isSending = true))
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState())
ensureAllEventsConsumed()
assertThat(previewState.voiceMessageState).isEqualTo(aPreviewState())
@ -573,7 +569,7 @@ class VoiceMessageComposerPresenterTest {
is VoiceMessageState.Preview -> when (state.isPlaying) {
// If the preview was playing, it pauses
true -> awaitItem().apply {
assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.1f, time = RECORDING_DURATION))
assertThat(voiceMessageState).isEqualTo(aPausedState())
}
false -> mostRecentState
}
@ -627,15 +623,37 @@ class VoiceMessageComposerPresenterTest {
isPlaying: Boolean = false,
playbackProgress: Float = 0f,
isSending: Boolean = false,
time: Duration = 0.seconds,
time: Duration = RECORDING_DURATION,
showCursor: Boolean = false,
waveform: List<Float> = voiceRecorder.waveform,
) = VoiceMessageState.Preview(
isPlaying = isPlaying,
playbackProgress = playbackProgress,
isSending = isSending,
time = time,
showCursor = showCursor,
waveform = waveform.toImmutableList(),
)
private fun aPlayingState() =
aPreviewState(
isPlaying = true,
playbackProgress = 0.1f,
showCursor = true,
time = RECORDING_DURATION,
)
private fun aPausedState() =
aPlayingState()
.copy(isPlaying = false)
private fun VoiceMessageState.Preview.toSendingState() =
copy(
isPlaying = false,
isSending = true,
showCursor = false,
time = RECORDING_DURATION,
)
}
private fun aReplyMode() = MessageComposerMode.Reply(A_USER_NAME, null, false, AN_EVENT_ID, A_MESSAGE)