diff --git a/changelog.d/1784.feature b/changelog.d/1784.feature
new file mode 100644
index 0000000000..8613d7c798
--- /dev/null
+++ b/changelog.d/1784.feature
@@ -0,0 +1 @@
+Update voice message recording behaviour. Instead of holding the record button, users can now tap the record button to start recording and tap again to stop recording.
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt
index e34a22eedc..b988d545a6 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt
@@ -32,7 +32,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.textcomposer.TextComposer
import io.element.android.libraries.textcomposer.model.Message
-import io.element.android.libraries.textcomposer.model.PressEvent
+import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.Suggestion
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import kotlinx.coroutines.launch
@@ -77,8 +77,8 @@ internal fun MessageComposerView(
}
}
- val onVoiceRecordButtonEvent = { press: PressEvent ->
- voiceMessageState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(press))
+ val onVoiceRecorderEvent = { press: VoiceMessageRecorderEvent ->
+ voiceMessageState.eventSink(VoiceMessageComposerEvents.RecorderEvent(press))
}
val onSendVoiceMessage = {
@@ -107,7 +107,7 @@ internal fun MessageComposerView(
onDismissTextFormatting = ::onDismissTextFormatting,
enableTextFormatting = enableTextFormatting,
enableVoiceMessages = enableVoiceMessages,
- onVoiceRecordButtonEvent = onVoiceRecordButtonEvent,
+ onVoiceRecorderEvent = onVoiceRecorderEvent,
onVoicePlayerEvent = onVoicePlayerEvent,
onSendVoiceMessage = onSendVoiceMessage,
onDeleteVoiceMessage = onDeleteVoiceMessage,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt
index f80ee15d95..0d384185df 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt
@@ -17,12 +17,12 @@
package io.element.android.features.messages.impl.voicemessages.composer
import androidx.lifecycle.Lifecycle
-import io.element.android.libraries.textcomposer.model.PressEvent
+import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
sealed interface VoiceMessageComposerEvents {
- data class RecordButtonEvent(
- val pressEvent: PressEvent
+ data class RecorderEvent(
+ val recorderEvent: VoiceMessageRecorderEvent
): VoiceMessageComposerEvents
data class PlayerEvent(
val playerEvent: VoiceMessagePlayerEvent,
diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt
index d39b065e5a..cd54dc5588 100644
--- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt
+++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt
@@ -37,7 +37,7 @@ import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsPresenter
-import io.element.android.libraries.textcomposer.model.PressEvent
+import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.voicerecorder.api.VoiceRecorder
@@ -95,10 +95,10 @@ class VoiceMessageComposerPresenter @Inject constructor(
}
}
- val onRecordButtonPress = { event: VoiceMessageComposerEvents.RecordButtonEvent ->
+ val onVoiceMessageRecorderEvent = { event: VoiceMessageComposerEvents.RecorderEvent ->
val permissionGranted = permissionState.permissionGranted
- when (event.pressEvent) {
- PressEvent.PressStart -> {
+ when (event.recorderEvent) {
+ VoiceMessageRecorderEvent.Start -> {
Timber.v("Voice message record button pressed")
when {
permissionGranted -> {
@@ -110,12 +110,12 @@ class VoiceMessageComposerPresenter @Inject constructor(
}
}
}
- PressEvent.LongPressEnd -> {
- Timber.v("Voice message record button released")
+ VoiceMessageRecorderEvent.Stop -> {
+ Timber.v("Voice message stop button pressed")
localCoroutineScope.finishRecording()
}
- PressEvent.Tapped -> {
- Timber.v("Voice message record button tapped")
+ VoiceMessageRecorderEvent.Cancel -> {
+ Timber.v("Voice message cancel button tapped")
localCoroutineScope.cancelRecording()
}
}
@@ -163,7 +163,7 @@ class VoiceMessageComposerPresenter @Inject constructor(
val handleEvents: (VoiceMessageComposerEvents) -> Unit = { event ->
when (event) {
- is VoiceMessageComposerEvents.RecordButtonEvent -> onRecordButtonPress(event)
+ is VoiceMessageComposerEvents.RecorderEvent -> onVoiceMessageRecorderEvent(event)
is VoiceMessageComposerEvents.PlayerEvent -> onPlayerEvent(event.playerEvent)
is VoiceMessageComposerEvents.SendVoiceMessage -> localCoroutineScope.launch {
onSendButtonPress()
diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
index 1a6ff988db..8fe1048c3c 100644
--- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
+++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
@@ -44,7 +44,7 @@ import io.element.android.libraries.permissions.api.aPermissionsState
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
import io.element.android.libraries.textcomposer.model.MessageComposerMode
-import io.element.android.libraries.textcomposer.model.PressEvent
+import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageState
import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder
@@ -99,7 +99,7 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
@@ -116,13 +116,13 @@ class VoiceMessageComposerPresenterTest {
presenter.present()
}.test {
awaitItem().apply {
- eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
assertThat(keepScreenOn).isFalse()
}
awaitItem().apply {
assertThat(keepScreenOn).isTrue()
- eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
}
val finalState = awaitItem().apply {
@@ -139,13 +139,11 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.Tapped))
-
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Cancel))
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
-
testPauseAndDestroy(finalState)
}
}
@@ -156,8 +154,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState())
@@ -173,7 +171,7 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
val finalState = awaitItem().apply {
this.eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
}
@@ -192,8 +190,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
val finalState = awaitItem().also {
assertThat(it.voiceMessageState).isEqualTo(aPlayingState())
@@ -210,8 +208,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Pause))
val finalState = awaitItem().also {
@@ -229,8 +227,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Seek(0.5f)))
awaitItem().apply {
assertThat(voiceMessageState).isEqualTo(aPreviewState(playbackProgress = 0.5f, time = 0.seconds, showCursor = true))
@@ -256,8 +254,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage)
val finalState = awaitItem()
@@ -274,8 +272,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage)
awaitItem().apply {
@@ -296,8 +294,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPreviewState().toSendingState())
@@ -318,15 +316,15 @@ class VoiceMessageComposerPresenterTest {
}.test {
// Send a normal voice message
messageComposerContext.composerMode = MessageComposerMode.Normal
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
skipItems(1) // Sending state
// Now reply with a voice message
messageComposerContext.composerMode = aReplyMode()
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
val finalState = awaitItem() // Sending state
@@ -345,8 +343,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().eventSink(VoiceMessageComposerEvents.PlayerEvent(VoiceMessagePlayerEvent.Play))
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
assertThat(awaitItem().voiceMessageState).isEqualTo(aPlayingState().toSendingState())
@@ -367,8 +365,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().run {
eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
@@ -392,8 +390,8 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
awaitItem().apply {
assertThat(voiceMessageState).isEqualTo(aPreviewState())
eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
@@ -417,8 +415,8 @@ class VoiceMessageComposerPresenterTest {
presenter.present()
}.test {
mediaPreProcessor.givenResult(Result.failure(Exception()))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
val previewState = awaitItem()
previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
@@ -467,7 +465,7 @@ class VoiceMessageComposerPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
- initialState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
assertThat(analyticsService.trackedErrors).containsExactly(
@@ -491,15 +489,15 @@ class VoiceMessageComposerPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
- initialState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
assertThat(awaitItem().voiceMessageState).isEqualTo(VoiceMessageState.Idle)
- initialState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
+ initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Stop))
voiceRecorder.assertCalls(stopped = 1)
permissionsPresenter.setPermissionGranted()
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
voiceRecorder.assertCalls(stopped = 1, started = 1)
@@ -519,7 +517,7 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
// See the dialog and accept it
awaitItem().also {
@@ -533,7 +531,7 @@ class VoiceMessageComposerPresenterTest {
permissionsPresenter.setPermissionGranted()
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
voiceRecorder.assertCalls(started = 1)
@@ -553,7 +551,7 @@ class VoiceMessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
- awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ awaitItem().eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
// See the dialog and accept it
awaitItem().also {
@@ -565,7 +563,7 @@ class VoiceMessageComposerPresenterTest {
// Dialog is hidden, user tries to record again
awaitItem().also {
assertThat(it.showPermissionRationaleDialog).isFalse()
- it.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
+ it.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
}
// Dialog is shown once again
diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_pause.xml b/libraries/designsystem/src/main/res/drawable/ic_pause.xml
similarity index 100%
rename from libraries/textcomposer/impl/src/main/res/drawable/ic_pause.xml
rename to libraries/designsystem/src/main/res/drawable/ic_pause.xml
diff --git a/libraries/textcomposer/impl/src/main/res/drawable/ic_play.xml b/libraries/designsystem/src/main/res/drawable/ic_play.xml
similarity index 100%
rename from libraries/textcomposer/impl/src/main/res/drawable/ic_play.xml
rename to libraries/designsystem/src/main/res/drawable/ic_play.xml
diff --git a/libraries/designsystem/src/main/res/drawable/ic_stop.xml b/libraries/designsystem/src/main/res/drawable/ic_stop.xml
new file mode 100644
index 0000000000..e4cd1507bb
--- /dev/null
+++ b/libraries/designsystem/src/main/res/drawable/ic_stop.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
index fef9d40a5b..9f02254b4d 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
@@ -66,7 +66,7 @@ import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.textcomposer.components.ComposerOptionsButton
import io.element.android.libraries.textcomposer.components.DismissTextFormattingButton
-import io.element.android.libraries.textcomposer.components.RecordButton
+import io.element.android.libraries.textcomposer.components.VoiceMessageRecorderButton
import io.element.android.libraries.textcomposer.components.SendButton
import io.element.android.libraries.textcomposer.components.TextFormatting
import io.element.android.libraries.textcomposer.components.VoiceMessageDeleteButton
@@ -75,7 +75,7 @@ import io.element.android.libraries.textcomposer.components.VoiceMessageRecordin
import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
import io.element.android.libraries.textcomposer.model.Message
import io.element.android.libraries.textcomposer.model.MessageComposerMode
-import io.element.android.libraries.textcomposer.model.PressEvent
+import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
import io.element.android.libraries.textcomposer.model.Suggestion
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageState
@@ -103,7 +103,7 @@ fun TextComposer(
onResetComposerMode: () -> Unit = {},
onAddAttachment: () -> Unit = {},
onDismissTextFormatting: () -> Unit = {},
- onVoiceRecordButtonEvent: (PressEvent) -> Unit = {},
+ onVoiceRecorderEvent: (VoiceMessageRecorderEvent) -> Unit = {},
onVoicePlayerEvent: (VoiceMessagePlayerEvent) -> Unit = {},
onSendVoiceMessage: () -> Unit = {},
onDeleteVoiceMessage: () -> Unit = {},
@@ -167,16 +167,15 @@ fun TextComposer(
)
}
val recordVoiceButton = @Composable {
- RecordButton(
- onPressStart = { onVoiceRecordButtonEvent(PressEvent.PressStart) },
- onLongPressEnd = { onVoiceRecordButtonEvent(PressEvent.LongPressEnd) },
- onTap = { onVoiceRecordButtonEvent(PressEvent.Tapped) },
+ VoiceMessageRecorderButton(
+ isRecording = voiceMessageState is VoiceMessageState.Recording,
+ onEvent = onVoiceRecorderEvent,
)
}
val sendVoiceButton = @Composable {
SendButton(
canSendMessage = voiceMessageState is VoiceMessageState.Preview,
- onClick = { onSendVoiceMessage() },
+ onClick = onSendVoiceMessage,
composerMode = composerMode,
)
}
@@ -223,8 +222,12 @@ fun TextComposer(
}
val voiceDeleteButton = @Composable {
- if (voiceMessageState is VoiceMessageState.Preview) {
- VoiceMessageDeleteButton(enabled = !voiceMessageState.isSending, onClick = onDeleteVoiceMessage)
+ when (voiceMessageState) {
+ is VoiceMessageState.Preview ->
+ VoiceMessageDeleteButton(enabled = !voiceMessageState.isSending, onClick = onDeleteVoiceMessage)
+ is VoiceMessageState.Recording ->
+ VoiceMessageDeleteButton(enabled = true, onClick = { onVoiceRecorderEvent(VoiceMessageRecorderEvent.Cancel) })
+ else -> {}
}
}
@@ -286,7 +289,7 @@ private fun StandardLayout(
verticalAlignment = Alignment.Bottom,
) {
if (enableVoiceMessages && voiceMessageState !is VoiceMessageState.Idle) {
- if (voiceMessageState is VoiceMessageState.Preview) {
+ if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) {
Box(
modifier = Modifier
.padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp)
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/RecordButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/RecordButton.kt
deleted file mode 100644
index 0a0710095e..0000000000
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/RecordButton.kt
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Copyright (c) 2023 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-@file:OptIn(ExperimentalMaterial3Api::class)
-
-package io.element.android.libraries.textcomposer.components
-
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.TooltipState
-import androidx.compose.material3.rememberTooltipState
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.hapticfeedback.HapticFeedbackType
-import androidx.compose.ui.input.pointer.PointerEventType
-import androidx.compose.ui.input.pointer.pointerInput
-import androidx.compose.ui.platform.LocalHapticFeedback
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import io.element.android.libraries.designsystem.components.tooltip.ElementTooltipDefaults
-import io.element.android.libraries.designsystem.components.tooltip.PlainTooltip
-import io.element.android.libraries.designsystem.components.tooltip.TooltipBox
-import io.element.android.libraries.designsystem.preview.ElementPreview
-import io.element.android.libraries.designsystem.preview.PreviewsDayNight
-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.designsystem.utils.CommonDrawables
-import io.element.android.libraries.textcomposer.R
-import io.element.android.libraries.textcomposer.utils.PressState
-import io.element.android.libraries.textcomposer.utils.PressStateEffects
-import io.element.android.libraries.textcomposer.utils.rememberPressState
-import io.element.android.libraries.theme.ElementTheme
-import io.element.android.libraries.ui.strings.CommonStrings
-import kotlinx.coroutines.launch
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-internal fun RecordButton(
- modifier: Modifier = Modifier,
- initialTooltipIsVisible: Boolean = false,
- onPressStart: () -> Unit = {},
- onLongPressEnd: () -> Unit = {},
- onTap: () -> Unit = {},
-) {
- val coroutineScope = rememberCoroutineScope()
- val pressState = rememberPressState()
- val hapticFeedback = LocalHapticFeedback.current
-
- val performHapticFeedback = {
- hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
- }
-
- val tooltipState = rememberTooltipState(
- initialIsVisible = initialTooltipIsVisible
- )
-
- PressStateEffects(
- pressState = pressState.value,
- onPressStart = {
- onPressStart()
- performHapticFeedback()
- },
- onLongPressEnd = {
- onLongPressEnd()
- performHapticFeedback()
- },
- onTap = {
- onTap()
- performHapticFeedback()
- coroutineScope.launch { tooltipState.show() }
- },
- )
- Box(modifier = modifier) {
- HoldToRecordTooltip(
- tooltipState = tooltipState,
- spacingBetweenTooltipAndAnchor = 0.dp, // Accounts for the 48.dp size of the record button
- anchor = {
- RecordButtonView(
- isPressed = pressState.value is PressState.Pressing,
- modifier = Modifier
- .pointerInput(Unit) {
- awaitPointerEventScope {
- while (true) {
- val event = awaitPointerEvent()
- coroutineScope.launch {
- when (event.type) {
- PointerEventType.Press -> pressState.press()
- PointerEventType.Release -> pressState.release()
- }
- }
- }
- }
- }
- )
- }
- )
- }
-}
-
-@Composable
-private fun RecordButtonView(
- isPressed: Boolean,
- modifier: Modifier = Modifier,
-) {
- IconButton(
- modifier = modifier
- .size(48.dp),
- onClick = {},
- ) {
- Icon(
- modifier = Modifier.size(24.dp),
- resourceId = if (isPressed) {
- CommonDrawables.ic_compound_mic_on_solid
- } else {
- CommonDrawables.ic_compound_mic_on_outline
- },
- contentDescription = stringResource(CommonStrings.a11y_voice_message_record),
- tint = ElementTheme.colors.iconSecondary,
- )
- }
-}
-
-@Composable
-private fun HoldToRecordTooltip(
- tooltipState: TooltipState,
- spacingBetweenTooltipAndAnchor: Dp,
- modifier: Modifier = Modifier,
- anchor: @Composable () -> Unit,
-) {
- TooltipBox(
- positionProvider = ElementTooltipDefaults.rememberPlainTooltipPositionProvider(
- spacingBetweenTooltipAndAnchor = spacingBetweenTooltipAndAnchor,
- ),
- tooltip = {
- PlainTooltip {
- Text(
- text = stringResource(R.string.screen_room_voice_message_tooltip),
- color = ElementTheme.colors.textOnSolidPrimary,
- style = ElementTheme.typography.fontBodySmMedium,
- )
- }
- },
- state = tooltipState,
- modifier = modifier,
- focusable = false,
- enableUserInput = false,
- content = anchor,
- )
-}
-
-@PreviewsDayNight
-@Composable
-internal fun RecordButtonPreview() = ElementPreview {
- Row {
- RecordButtonView(isPressed = false)
- RecordButtonView(isPressed = true)
- }
-}
-
-@PreviewsDayNight
-@Composable
-internal fun HoldToRecordTooltipPreview() = ElementPreview {
- Box(modifier = Modifier.fillMaxSize()) {
- RecordButton(
- modifier = Modifier.align(Alignment.BottomEnd),
- initialTooltipIsVisible = true,
- )
- }
-}
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt
index 222ee2aeaf..bcf307ec2e 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessagePreview.kt
@@ -43,7 +43,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
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.designsystem.utils.CommonDrawables
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.libraries.ui.utils.time.formatShort
@@ -145,14 +145,14 @@ private fun PlayerButton(
@Composable
private fun PauseIcon() = Icon(
- resourceId = R.drawable.ic_pause,
+ resourceId = CommonDrawables.ic_pause,
contentDescription = stringResource(id = CommonStrings.a11y_pause),
modifier = Modifier.size(20.dp),
)
@Composable
private fun PlayIcon() = Icon(
- resourceId = R.drawable.ic_play,
+ resourceId = CommonDrawables.ic_play,
contentDescription = stringResource(id = CommonStrings.a11y_play),
modifier = Modifier.size(20.dp),
)
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt
new file mode 100644
index 0000000000..d2a8c2cae1
--- /dev/null
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageRecorderButton.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2023 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package io.element.android.libraries.textcomposer.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.hapticfeedback.HapticFeedbackType
+import androidx.compose.ui.platform.LocalHapticFeedback
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import io.element.android.libraries.designsystem.preview.ElementPreview
+import io.element.android.libraries.designsystem.preview.PreviewsDayNight
+import io.element.android.libraries.designsystem.theme.components.Icon
+import io.element.android.libraries.designsystem.theme.components.IconButton
+import io.element.android.libraries.designsystem.utils.CommonDrawables
+import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
+import io.element.android.libraries.theme.ElementTheme
+import io.element.android.libraries.ui.strings.CommonStrings
+
+@Composable
+internal fun VoiceMessageRecorderButton(
+ isRecording: Boolean,
+ modifier: Modifier = Modifier,
+ onEvent: (VoiceMessageRecorderEvent) -> Unit = {},
+) {
+ val hapticFeedback = LocalHapticFeedback.current
+
+ val performHapticFeedback = {
+ hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
+ }
+
+ if (isRecording) {
+ StopButton(
+ modifier = modifier,
+ onClick = {
+ performHapticFeedback()
+ onEvent(VoiceMessageRecorderEvent.Stop)
+ }
+ )
+ } else {
+ StartButton(
+ modifier = modifier,
+ onClick = {
+ performHapticFeedback()
+ onEvent(VoiceMessageRecorderEvent.Start)
+ }
+ )
+ }
+}
+
+@Composable
+private fun StartButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) = IconButton(
+ modifier = modifier.size(48.dp),
+ onClick = onClick,
+) {
+ Icon(
+ modifier = Modifier.size(24.dp),
+ resourceId = CommonDrawables.ic_compound_mic_on_outline,
+ contentDescription = stringResource(CommonStrings.a11y_voice_message_record),
+ tint = ElementTheme.colors.iconSecondary,
+ )
+}
+
+@Composable
+private fun StopButton(
+ onClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) = IconButton(
+ modifier = modifier
+ .size(48.dp),
+ onClick = onClick,
+) {
+ Box(
+ Modifier
+ .size(36.dp)
+ .background(
+ color = ElementTheme.colors.bgActionPrimaryRest,
+ shape = CircleShape,
+ )
+ )
+ Icon(
+ modifier = Modifier.size(24.dp),
+ resourceId = CommonDrawables.ic_stop,
+ contentDescription = stringResource(CommonStrings.a11y_voice_message_stop_recording),
+ tint = ElementTheme.colors.iconOnSolidPrimary,
+ )
+}
+
+@PreviewsDayNight
+@Composable
+internal fun VoiceMessageRecorderButtonPreview() = ElementPreview {
+ Row {
+ VoiceMessageRecorderButton(isRecording = false)
+ VoiceMessageRecorderButton(isRecording = true)
+ }
+}
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/PressEvent.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt
similarity index 77%
rename from libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/PressEvent.kt
rename to libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt
index 340540886d..17091930c5 100644
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/PressEvent.kt
+++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageRecorderEvent.kt
@@ -16,8 +16,8 @@
package io.element.android.libraries.textcomposer.model
-sealed interface PressEvent {
- data object PressStart: PressEvent
- data object Tapped: PressEvent
- data object LongPressEnd: PressEvent
+sealed interface VoiceMessageRecorderEvent {
+ data object Start: VoiceMessageRecorderEvent
+ data object Stop: VoiceMessageRecorderEvent
+ data object Cancel: VoiceMessageRecorderEvent
}
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressState.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressState.kt
deleted file mode 100644
index 50df6d591c..0000000000
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressState.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (c) 2023 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.element.android.libraries.textcomposer.utils
-
-/**
- * State of a press gesture.
- */
-internal sealed interface PressState {
- data class Idle(
- val lastPress: Pressing?
- ) : PressState
-
- sealed interface Pressing : PressState
- data object Tapping : Pressing
- data object LongPressing : Pressing
-}
-
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateEffects.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateEffects.kt
deleted file mode 100644
index aaee6bae0f..0000000000
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateEffects.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (c) 2023 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.element.android.libraries.textcomposer.utils
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-
-/**
- * React to [PressState] changes.
- */
-@Composable
-internal fun PressStateEffects(
- pressState: PressState,
- onPressStart: () -> Unit = {},
- onLongPressStart: () -> Unit = {},
- onTap: () -> Unit = {},
- onLongPressEnd: () -> Unit = {},
-) {
- LaunchedEffect(pressState) {
- when (pressState) {
- is PressState.Idle ->
- when (pressState.lastPress) {
- PressState.Tapping -> onTap()
- PressState.LongPressing -> onLongPressEnd()
- null -> {} // Do nothing
- }
- is PressState.LongPressing -> onLongPressStart()
- PressState.Tapping -> onPressStart()
- }
- }
-}
-
-
diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolder.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolder.kt
deleted file mode 100644
index 7021b8ac46..0000000000
--- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolder.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (c) 2023 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.element.android.libraries.textcomposer.utils
-
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalViewConfiguration
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.isActive
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.yield
-import timber.log.Timber
-
-@Composable
-internal fun rememberPressState(
- longPressTimeoutMillis: Long = LocalViewConfiguration.current.longPressTimeoutMillis,
-): PressStateHolder {
- return remember(longPressTimeoutMillis) {
- PressStateHolder(longPressTimeoutMillis = longPressTimeoutMillis)
- }
-}
-
-/**
- * State machine that keeps track of the pressed state.
- *
- * When a press is started, the state will transition through:
- * [PressState.Idle] -> [PressState.Tapping] -> ...
- *
- * If a press is held for a longer time, the state will continue through:
- * ... -> [PressState.LongPressing] -> ...
- *
- * When the press is released the states will then transition back to idle.
- * ... -> [PressState.Idle]
- *
- * Whether a press should be considered a tap or a long press can be determined by
- * looking at the last press when in the idle state.
- *
- * @see [PressStateEffects]
- * @see [rememberPressState]
- */
-internal class PressStateHolder(
- private val longPressTimeoutMillis: Long,
-) : State {
- private var state: PressState by mutableStateOf(PressState.Idle(lastPress = null))
-
- override val value: PressState
- get() = state
-
- private var longPressTimer: Job? = null
-
- suspend fun press() = coroutineScope {
- when (state) {
- is PressState.Idle -> {
- state = PressState.Tapping
- }
- is PressState.Pressing ->
- Timber.e("Pointer pressed but it has not been released")
- }
-
- longPressTimer = launch {
- delay(longPressTimeoutMillis)
- yield()
-
- if (isActive && state == PressState.Tapping) {
- state = PressState.LongPressing
- }
- }
- }
-
- fun release() {
- longPressTimer?.cancel()
- longPressTimer = null
- when (val lastState = state) {
- is PressState.Pressing ->
- state = PressState.Idle(lastPress = lastState)
- is PressState.Idle ->
- Timber.e("Pointer pressed but it has not been released")
- }
- }
-}
-
diff --git a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolderTest.kt b/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolderTest.kt
deleted file mode 100644
index 615692911e..0000000000
--- a/libraries/textcomposer/impl/src/test/kotlin/io/element/android/libraries/textcomposer/utils/PressStateHolderTest.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (c) 2023 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.element.android.libraries.textcomposer.utils
-
-import com.google.common.truth.Truth.assertThat
-import io.element.android.libraries.textcomposer.utils.PressState.Idle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.async
-import kotlinx.coroutines.test.advanceTimeBy
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import kotlin.time.Duration.Companion.milliseconds
-
-@OptIn(ExperimentalCoroutinesApi::class) class PressStateHolderTest {
- companion object {
- const val LONG_PRESS_TIMEOUT_MILLIS = 1L
- }
- @Test
- fun `it starts in idle state`() = runTest {
- val stateHolder = createStateHolder()
- assertThat(stateHolder.value).isEqualTo(Idle(lastPress = null))
- }
-
- @Test
- fun `when press, it moves to tapping state`() = runTest {
- val stateHolder = createStateHolder()
- val press = async { stateHolder.press() }
- advanceTimeBy(1.milliseconds)
- assertThat(stateHolder.value).isEqualTo(PressState.Tapping)
- press.await()
- }
-
- @Test
- fun `when release after short delay, it moves through tap states`() = runTest {
- val stateHolder = createStateHolder()
- val press = async { stateHolder.press() }
- advanceTimeBy(1.milliseconds)
- assertThat(stateHolder.value).isEqualTo(PressState.Tapping)
- stateHolder.release()
- advanceTimeBy(1.milliseconds) // wait for the long press timeout which should not be triggered
- assertThat(stateHolder.value).isEqualTo(Idle(lastPress = PressState.Tapping))
- press.await()
- }
-
- @Test
- fun `when hold, it moves through long press states`() = runTest {
- val stateHolder = createStateHolder()
- val press = async { stateHolder.press() }
- advanceTimeBy(1.milliseconds)
- assertThat(stateHolder.value).isEqualTo(PressState.Tapping)
- advanceTimeBy(1.milliseconds)
- assertThat(stateHolder.value).isEqualTo(PressState.LongPressing)
- stateHolder.release()
- assertThat(stateHolder.value).isEqualTo(Idle(lastPress = PressState.LongPressing))
- press.await()
- }
-
- @Test
- fun `when release and repress, it doesn't enter long press states`() = runTest {
- val stateHolder = createStateHolder()
- val press1 = async { stateHolder.press() }
- advanceTimeBy(1.milliseconds)
- assertThat(stateHolder.value).isEqualTo(PressState.Tapping)
- stateHolder.release()
- val press2 = async { stateHolder.press() }
- advanceTimeBy(1.milliseconds)
- assertThat(stateHolder.value).isEqualTo(PressState.Tapping)
- press1.await()
- press2.await()
- }
-
- @Test
- fun `when press twice without releasing, it doesn't throw an error`() = runTest {
- val stateHolder = createStateHolder()
- stateHolder.press()
- stateHolder.press()
- }
-
- @Test
- fun `when release without first pressing, it doesn't throw an error`() = runTest {
- val stateHolder = createStateHolder()
- stateHolder.release()
- }
-
- @Test
- fun `when release twice without pressing, it doesn't throw an error `() = runTest {
- val stateHolder = createStateHolder()
- stateHolder.press()
- stateHolder.release()
- stateHolder.release()
- }
-
- private fun createStateHolder() =
- PressStateHolder(
- LONG_PRESS_TIMEOUT_MILLIS,
- )
-}
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Day-6_6_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Day-6_6_null_0,NEXUS_5,1.0,en].png
index 3ec6004b6d..521717b05b 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Day-6_6_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Day-6_6_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:3d5209087d7841a80f7213f26aaf8322cc5fee03466b9059e06b0daf5f489c1b
-size 10012
+oid sha256:5cd4e624c0ed8abe5d22d60992f64f81e91fa41ea7d8772acc5e3832eadbcbb7
+size 10581
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Night-6_7_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Night-6_7_null_0,NEXUS_5,1.0,en].png
index dcddb1ae01..3dd0888ff4 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Night-6_7_null_0,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl.messagecomposer_MessageComposerViewVoice_null_MessageComposerViewVoice-Night-6_7_null_0,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:5b8fca1de81e424f01a518e3a2547b58c0e9eb7249327f3fcdb1cf62ab655972
-size 9429
+oid sha256:01eda47acbf273bf61e60dbbc466aac5e9a4eac7690af4cde2e06e7e1f190bcf
+size 10152
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Day-13_13_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Day-13_13_null,NEXUS_5,1.0,en].png
deleted file mode 100644
index 2f3dc97e8c..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Day-13_13_null,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:e6d75686b1463d11d89e5130b160c3c446f26612e8da44981cc6994687001d80
-size 7093
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Night-13_14_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Night-13_14_null,NEXUS_5,1.0,en].png
deleted file mode 100644
index ff6633e96a..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_HoldToRecordTooltip_null_HoldToRecordTooltip-Night-13_14_null,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f8c96b9ce6a8d9136c56789e9e6af25b4259143f6416305c523f4e5224bc8fa8
-size 5898
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Day-12_12_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Day-12_12_null,NEXUS_5,1.0,en].png
deleted file mode 100644
index f0c42b1810..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Day-12_12_null,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:a0e8a1efaf22dd86e6e27904d72a820cfb0b5d1d38ffeb5745bfa6ca3b0a1c85
-size 6003
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Night-12_13_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Night-12_13_null,NEXUS_5,1.0,en].png
deleted file mode 100644
index 27368109ad..0000000000
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_RecordButton_null_RecordButton-Night-12_13_null,NEXUS_5,1.0,en].png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:7f2db2983459eae8b7538c51d125836807b29334b01fbca495c3c9630fb510c2
-size 5969
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Day-14_14_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Day-12_12_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Day-14_14_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Day-12_12_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Night-14_15_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Night-12_13_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Night-14_15_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_SendButton_null_SendButton-Night-12_13_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Day-15_15_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Day-13_13_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Day-15_15_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Day-13_13_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Night-15_16_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Night-13_14_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Night-15_16_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_TextFormatting_null_TextFormatting-Night-13_14_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Day-16_16_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Day-14_14_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Day-16_16_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Day-14_14_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Night-16_17_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Night-14_15_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Night-16_17_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageDeleteButton_null_VoiceMessageDeleteButton-Night-14_15_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Day-17_17_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Day-15_15_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Day-17_17_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Day-15_15_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Night-17_18_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Night-15_16_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Night-17_18_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessagePreview_null_VoiceMessagePreview-Night-15_16_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Day-16_16_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Day-16_16_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..3059b1e5ad
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Day-16_16_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6b6f6caef8d902d4e0b93721f212dcd3f20cde267e1782813f0c55498bca5c8e
+size 6805
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Night-16_17_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Night-16_17_null,NEXUS_5,1.0,en].png
new file mode 100644
index 0000000000..279ced000a
--- /dev/null
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecorderButton_null_VoiceMessageRecorderButton-Night-16_17_null,NEXUS_5,1.0,en].png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6def3059e37f5c7d6d8930df229a895f5d37b95f46a138fa4ef790bbf76b80b6
+size 6744
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Day-18_18_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Day-17_17_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Day-18_18_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Day-17_17_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Night-18_19_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Night-17_18_null,NEXUS_5,1.0,en].png
similarity index 100%
rename from tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Night-18_19_null,NEXUS_5,1.0,en].png
rename to tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_VoiceMessageRecording_null_VoiceMessageRecording-Night-17_18_null,NEXUS_5,1.0,en].png
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Day-4_4_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Day-4_4_null,NEXUS_5,1.0,en].png
index f5ba129fd3..e9254642a2 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Day-4_4_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Day-4_4_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:991416dcd21ebf8e2d57baac7cb42506596a1a346dd7d643b09c84c473e053b7
-size 28016
+oid sha256:9a76e1dcbc4461b6e0a1cca836a097846262a3d302befbec4486ed7ee837a8da
+size 28214
diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Night-4_5_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Night-4_5_null,NEXUS_5,1.0,en].png
index 8bd3ded996..304d6dc23e 100644
--- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Night-4_5_null,NEXUS_5,1.0,en].png
+++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_TextComposerVoice_null_TextComposerVoice-Night-4_5_null,NEXUS_5,1.0,en].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:a8b8c60473ce1b9e7451e763c960a9242bb83ca1d3769ee921db830ff6e57e7c
-size 27094
+oid sha256:25be7fa172232c508c58369f60e82fc7ec1d7362d9f701c1e698ec8d8498b482
+size 27449