Display duration of recorded voice message (#1733)
--------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
14dc8e1059
commit
ddc1e1d0cc
12 changed files with 119 additions and 40 deletions
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue