Skip to content

Commit

Permalink
Add time to voice message composer UI (#1720)
Browse files Browse the repository at this point in the history
---------

Co-authored-by: ElementBot <[email protected]>
  • Loading branch information
jonnyandrew and ElementBot authored Nov 2, 2023
1 parent 8f5c617 commit 83a6395
Show file tree
Hide file tree
Showing 10 changed files with 50 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ class VoiceMessageComposerPlayer @Inject constructor(
* The progress of this player between 0 and 1.
*/
val progress: Float =
if (duration <= currentPosition) 0f else currentPosition.toFloat() / duration.toFloat()
if (duration == 0L)
0f
else
(currentPosition.toFloat() / duration.toFloat())
.coerceAtMost(1f) // Current position may exceed reported duration
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds

@SingleIn(RoomScope::class)
class VoiceMessageComposerPresenter @Inject constructor(
Expand Down Expand Up @@ -191,6 +192,7 @@ class VoiceMessageComposerPresenter @Inject constructor(
isSending = isSending,
isPlaying = isPlaying,
playbackProgress = playerState.progress,
time = playerState.currentPosition.milliseconds,
waveform = waveform,
)
else -> VoiceMessageState.Idle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.collections.immutable.toPersistentList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

class VoiceMessageComposerPresenterTest {
Expand Down Expand Up @@ -195,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))
assertThat(it.voiceMessageState).isEqualTo(aPreviewState(isPlaying = true, playbackProgress = 0.1f, time = RECORDING_DURATION))
}
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)

Expand All @@ -214,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))
assertThat(it.voiceMessageState).isEqualTo(aPreviewState(isPlaying = false, playbackProgress = 0.1f, time = RECORDING_DURATION))
}
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)

Expand Down Expand Up @@ -251,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))
assertThat(voiceMessageState).isEqualTo(aPreviewState(isPlaying = false, playbackProgress = 0.1f, time = RECORDING_DURATION))
}

val finalState = awaitItem()
Expand Down Expand Up @@ -321,9 +322,11 @@ 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
))
assertThat(awaitItem().voiceMessageState).isEqualTo(
aPreviewState(
isSending = true, isPlaying = false, playbackProgress = 0.1f, time = RECORDING_DURATION
)
)

val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
Expand Down Expand Up @@ -570,7 +573,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))
assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.1f, time = RECORDING_DURATION))
}
false -> mostRecentState
}
Expand Down Expand Up @@ -624,11 +627,13 @@ class VoiceMessageComposerPresenterTest {
isPlaying: Boolean = false,
playbackProgress: Float = 0f,
isSending: Boolean = false,
time: Duration = 0.seconds,
waveform: List<Float> = voiceRecorder.waveform,
) = VoiceMessageState.Preview(
isPlaying = isPlaying,
playbackProgress = playbackProgress,
isSending = isSending,
time = time,
waveform = waveform.toImmutableList(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ import io.element.android.wysiwyg.compose.RichTextEditor
import io.element.android.wysiwyg.compose.RichTextEditorState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import uniffi.wysiwyg_composer.MenuAction
import kotlin.time.Duration.Companion.seconds

Expand Down Expand Up @@ -211,6 +210,7 @@ fun TextComposer(
isPlaying = voiceMessageState.isPlaying,
waveform = voiceMessageState.waveform,
playbackProgress = voiceMessageState.playbackProgress,
time = voiceMessageState.time,
onPlayClick = onPlayVoiceMessageClicked,
onPauseClick = onPauseVoiceMessageClicked,
onSeek = onSeekVoiceMessage,
Expand Down Expand Up @@ -816,13 +816,14 @@ internal fun TextComposerVoicePreview() = ElementPreview {
enableVoiceMessages = true,
)
PreviewColumn(items = persistentListOf({
VoicePreview(voiceMessageState = VoiceMessageState.Recording(61.seconds, List(100) { it.toFloat() / 100 }.toPersistentList()))
VoicePreview(voiceMessageState = VoiceMessageState.Recording(61.seconds, createFakeWaveform()))
}, {
VoicePreview(
voiceMessageState = VoiceMessageState.Preview(
isSending = false,
isPlaying = false,
waveform = createFakeWaveform(),
time = 0.seconds,
playbackProgress = 0.0f
)
)
Expand All @@ -832,6 +833,7 @@ internal fun TextComposerVoicePreview() = ElementPreview {
isSending = false,
isPlaying = true,
waveform = createFakeWaveform(),
time = 3.seconds,
playbackProgress = 0.2f
)
)
Expand All @@ -841,6 +843,7 @@ internal fun TextComposerVoicePreview() = ElementPreview {
isSending = true,
isPlaying = false,
waveform = createFakeWaveform(),
time = 61.seconds,
playbackProgress = 0.0f
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.media.WaveformPlaybackView
import io.element.android.libraries.designsystem.components.media.createFakeWaveform
Expand All @@ -42,16 +43,21 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.text.applyScaleUp
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.IconButton
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.textcomposer.R
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.time.formatShort
import kotlinx.collections.immutable.ImmutableList
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@Composable
internal fun VoiceMessagePreview(
isInteractive: Boolean,
isPlaying: Boolean,
waveform: ImmutableList<Float>,
time: Duration,
modifier: Modifier = Modifier,
playbackProgress: Float = 0f,
onPlayClick: () -> Unit = {},
Expand Down Expand Up @@ -85,7 +91,13 @@ internal fun VoiceMessagePreview(

Spacer(modifier = Modifier.width(8.dp))

// TODO Add timer UI
Text(
text = time.formatShort(),
color = ElementTheme.materialColors.secondary,
style = ElementTheme.typography.fontBodySmMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)

Spacer(modifier = Modifier.width(12.dp))

Expand Down Expand Up @@ -151,8 +163,8 @@ internal fun VoiceMessagePreviewPreview() = ElementPreview {
Column(
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
VoiceMessagePreview(isInteractive = true, isPlaying = true, waveform = createFakeWaveform())
VoiceMessagePreview(isInteractive = true, isPlaying = false, waveform = createFakeWaveform())
VoiceMessagePreview(isInteractive = false, isPlaying = false, waveform = createFakeWaveform())
VoiceMessagePreview(isInteractive = true, isPlaying = true, time = 2.seconds, playbackProgress = 0.2f, waveform = createFakeWaveform())
VoiceMessagePreview(isInteractive = true, isPlaying = false, time = 0.seconds, playbackProgress = 0.0f, waveform = createFakeWaveform())
VoiceMessagePreview(isInteractive = false, isPlaying = false, time = 789.seconds, playbackProgress = 0.0f, waveform = createFakeWaveform())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ sealed class VoiceMessageState {
val isSending: Boolean,
val isPlaying: Boolean,
val playbackProgress: Float,
val time: Duration,
val waveform: ImmutableList<Float>,
): VoiceMessageState()

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 83a6395

Please sign in to comment.