Browse Source

Allow deleting a recorded voice message (#1635)

---------

Co-authored-by: ElementBot <benoitm+elementbot@element.io>
pull/1637/head
jonnyandrew 11 months ago committed by GitHub
parent
commit
8c7a0c0e0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt
  2. 1
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt
  3. 5
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt
  4. 33
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/VoiceMessageComposerPresenterTest.kt
  5. 32
      libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt
  6. 66
      libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageDeleteButton.kt
  7. 1
      libraries/voicerecorder/test/build.gradle.kts
  8. 20
      libraries/voicerecorder/test/src/main/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceRecorder.kt
  9. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageDeleteButton-D-14_14_null,NEXUS_5,1.0,en].png
  10. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageDeleteButton-N-14_15_null,NEXUS_5,1.0,en].png
  11. 0
      tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessagePreview-D-15_15_null,NEXUS_5,1.0,en].png
  12. 0
      tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessagePreview-N-15_16_null,NEXUS_5,1.0,en].png
  13. 0
      tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageRecording-D-16_16_null,NEXUS_5,1.0,en].png
  14. 0
      tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageRecording-N-16_17_null,NEXUS_5,1.0,en].png
  15. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerVoice-D-4_4_null,NEXUS_5,1.0,en].png
  16. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerVoice-N-4_5_null,NEXUS_5,1.0,en].png

9
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerView.kt

@ -75,10 +75,14 @@ internal fun MessageComposerView(
voiceMessageState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(press)) voiceMessageState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(press))
} }
fun onSendVoiceMessage() { val onSendVoiceMessage = {
voiceMessageState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) voiceMessageState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
} }
val onDeleteVoiceMessage = {
voiceMessageState.eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage)
}
TextComposer( TextComposer(
modifier = modifier, modifier = modifier,
state = state.richTextEditorState, state = state.richTextEditorState,
@ -94,7 +98,8 @@ internal fun MessageComposerView(
enableTextFormatting = enableTextFormatting, enableTextFormatting = enableTextFormatting,
enableVoiceMessages = enableVoiceMessages, enableVoiceMessages = enableVoiceMessages,
onVoiceRecordButtonEvent = onVoiceRecordButtonEvent, onVoiceRecordButtonEvent = onVoiceRecordButtonEvent,
onSendVoiceMessage = ::onSendVoiceMessage, onSendVoiceMessage = onSendVoiceMessage,
onDeleteVoiceMessage = onDeleteVoiceMessage,
onError = ::onError, onError = ::onError,
) )
} }

1
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerEvents.kt

@ -24,6 +24,7 @@ sealed interface VoiceMessageComposerEvents {
val pressEvent: PressEvent val pressEvent: PressEvent
): VoiceMessageComposerEvents ): VoiceMessageComposerEvents
data object SendVoiceMessage: VoiceMessageComposerEvents data object SendVoiceMessage: VoiceMessageComposerEvents
data object DeleteVoiceMessage: VoiceMessageComposerEvents
data object AcceptPermissionRationale: VoiceMessageComposerEvents data object AcceptPermissionRationale: VoiceMessageComposerEvents
data object DismissPermissionsRationale: VoiceMessageComposerEvents data object DismissPermissionsRationale: VoiceMessageComposerEvents
data class LifecycleEvent(val event: Lifecycle.Event): VoiceMessageComposerEvents data class LifecycleEvent(val event: Lifecycle.Event): VoiceMessageComposerEvents

5
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenter.kt

@ -134,6 +134,7 @@ class VoiceMessageComposerPresenter @Inject constructor(
is VoiceMessageComposerEvents.SendVoiceMessage -> localCoroutineScope.launch { is VoiceMessageComposerEvents.SendVoiceMessage -> localCoroutineScope.launch {
onSendButtonPress() onSendButtonPress()
} }
VoiceMessageComposerEvents.DeleteVoiceMessage -> localCoroutineScope.deleteRecording()
VoiceMessageComposerEvents.DismissPermissionsRationale -> onDismissPermissionsRationale() VoiceMessageComposerEvents.DismissPermissionsRationale -> onDismissPermissionsRationale()
VoiceMessageComposerEvents.AcceptPermissionRationale -> onAcceptPermissionsRationale() VoiceMessageComposerEvents.AcceptPermissionRationale -> onAcceptPermissionsRationale()
is VoiceMessageComposerEvents.LifecycleEvent -> onLifecycleEvent(event.event) is VoiceMessageComposerEvents.LifecycleEvent -> onLifecycleEvent(event.event)
@ -175,6 +176,10 @@ class VoiceMessageComposerPresenter @Inject constructor(
voiceRecorder.stopRecord(cancelled = true) voiceRecorder.stopRecord(cancelled = true)
} }
private fun CoroutineScope.deleteRecording() = launch {
voiceRecorder.deleteRecording()
}
private fun CoroutineScope.sendMessage( private fun CoroutineScope.sendMessage(
file: File, mimeType: String, file: File, mimeType: String,
) = launch { ) = launch {

33
features/messages/impl/src/test/kotlin/io/element/android/features/messages/voicemessages/VoiceMessageComposerPresenterTest.kt

@ -74,6 +74,7 @@ class VoiceMessageComposerPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
voiceRecorder.assertCalls(started = 0)
testPauseAndDestroy(initialState) testPauseAndDestroy(initialState)
} }
@ -89,6 +90,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
voiceRecorder.assertCalls(started = 1)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -105,6 +107,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -121,6 +124,25 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Preview) assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Preview)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)
testPauseAndDestroy(finalState)
}
}
@Test
fun `present - delete recording`() = runTest {
val presenter = createVoiceMessageComposerPresenter()
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
awaitItem().eventSink(VoiceMessageComposerEvents.DeleteVoiceMessage)
val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -140,6 +162,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
assertThat(matrixRoom.sendMediaCount).isEqualTo(1) assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -162,6 +185,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
assertThat(matrixRoom.sendMediaCount).isEqualTo(1) assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -186,6 +210,7 @@ class VoiceMessageComposerPresenterTest {
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Sending) assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Sending)
assertThat(matrixRoom.sendMediaCount).isEqualTo(0) assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
assertThat(analyticsService.trackedErrors).hasSize(0) assertThat(analyticsService.trackedErrors).hasSize(0)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -215,6 +240,7 @@ class VoiceMessageComposerPresenterTest {
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
assertThat(matrixRoom.sendMediaCount).isEqualTo(1) assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -233,6 +259,7 @@ class VoiceMessageComposerPresenterTest {
assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
assertThat(matrixRoom.sendMediaCount).isEqualTo(0) assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
assertThat(analyticsService.trackedErrors).hasSize(1) assertThat(analyticsService.trackedErrors).hasSize(1)
voiceRecorder.assertCalls(started = 0)
testPauseAndDestroy(initialState) testPauseAndDestroy(initialState)
} }
@ -253,6 +280,7 @@ class VoiceMessageComposerPresenterTest {
assertThat(analyticsService.trackedErrors).containsExactly( assertThat(analyticsService.trackedErrors).containsExactly(
VoiceMessageException.PermissionMissing(message = "Expected permission to record but none", cause = exception) VoiceMessageException.PermissionMissing(message = "Expected permission to record but none", cause = exception)
) )
voiceRecorder.assertCalls(started = 1)
testPauseAndDestroy(initialState) testPauseAndDestroy(initialState)
} }
@ -274,11 +302,14 @@ class VoiceMessageComposerPresenterTest {
assertThat(awaitItem().voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(awaitItem().voiceMessageState).isEqualTo(VoiceMessageState.Idle)
initialState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd)) initialState.eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
voiceRecorder.assertCalls(stopped = 1)
permissionsPresenter.setPermissionGranted() permissionsPresenter.setPermissionGranted()
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
voiceRecorder.assertCalls(stopped = 1, started = 1)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -312,6 +343,7 @@ class VoiceMessageComposerPresenterTest {
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart)) awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE) assertThat(finalState.voiceMessageState).isEqualTo(RECORDING_STATE)
voiceRecorder.assertCalls(started = 1)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }
@ -348,6 +380,7 @@ class VoiceMessageComposerPresenterTest {
assertThat(it.voiceMessageState).isEqualTo(VoiceMessageState.Idle) assertThat(it.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
assertThat(it.showPermissionRationaleDialog).isTrue() assertThat(it.showPermissionRationaleDialog).isTrue()
} }
voiceRecorder.assertCalls(started = 0)
testPauseAndDestroy(finalState) testPauseAndDestroy(finalState)
} }

32
libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt

@ -65,10 +65,11 @@ import io.element.android.libraries.testtags.testTag
import io.element.android.libraries.textcomposer.components.ComposerOptionsButton import io.element.android.libraries.textcomposer.components.ComposerOptionsButton
import io.element.android.libraries.textcomposer.components.DismissTextFormattingButton import io.element.android.libraries.textcomposer.components.DismissTextFormattingButton
import io.element.android.libraries.textcomposer.components.RecordButton import io.element.android.libraries.textcomposer.components.RecordButton
import io.element.android.libraries.textcomposer.components.VoiceMessagePreview
import io.element.android.libraries.textcomposer.components.VoiceMessageRecording
import io.element.android.libraries.textcomposer.components.SendButton import io.element.android.libraries.textcomposer.components.SendButton
import io.element.android.libraries.textcomposer.components.TextFormatting import io.element.android.libraries.textcomposer.components.TextFormatting
import io.element.android.libraries.textcomposer.components.VoiceMessageDeleteButton
import io.element.android.libraries.textcomposer.components.VoiceMessagePreview
import io.element.android.libraries.textcomposer.components.VoiceMessageRecording
import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape import io.element.android.libraries.textcomposer.components.textInputRoundedCornerShape
import io.element.android.libraries.textcomposer.model.Message import io.element.android.libraries.textcomposer.model.Message
import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.MessageComposerMode
@ -99,6 +100,7 @@ fun TextComposer(
onDismissTextFormatting: () -> Unit = {}, onDismissTextFormatting: () -> Unit = {},
onVoiceRecordButtonEvent: (PressEvent) -> Unit = {}, onVoiceRecordButtonEvent: (PressEvent) -> Unit = {},
onSendVoiceMessage: () -> Unit = {}, onSendVoiceMessage: () -> Unit = {},
onDeleteVoiceMessage: () -> Unit = {},
onError: (Throwable) -> Unit = {}, onError: (Throwable) -> Unit = {},
) { ) {
val onSendClicked = { val onSendClicked = {
@ -187,6 +189,16 @@ fun TextComposer(
} }
} }
val voiceDeleteButton = @Composable {
val enabled = when (voiceMessageState) {
VoiceMessageState.Preview -> true
VoiceMessageState.Sending,
is VoiceMessageState.Recording,
VoiceMessageState.Idle -> false
}
VoiceMessageDeleteButton(enabled = enabled, onClick = onDeleteVoiceMessage)
}
if (showTextFormatting) { if (showTextFormatting) {
TextFormattingLayout( TextFormattingLayout(
modifier = layoutModifier, modifier = layoutModifier,
@ -206,6 +218,7 @@ fun TextComposer(
textInput = textInput, textInput = textInput,
endButton = sendOrRecordButton, endButton = sendOrRecordButton,
voiceRecording = voiceRecording, voiceRecording = voiceRecording,
voiceDeleteButton = voiceDeleteButton,
) )
} }
@ -225,6 +238,7 @@ private fun StandardLayout(
textInput: @Composable () -> Unit, textInput: @Composable () -> Unit,
composerOptionsButton: @Composable () -> Unit, composerOptionsButton: @Composable () -> Unit,
voiceRecording: @Composable () -> Unit, voiceRecording: @Composable () -> Unit,
voiceDeleteButton: @Composable () -> Unit,
endButton: @Composable () -> Unit, endButton: @Composable () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
@ -233,9 +247,21 @@ private fun StandardLayout(
verticalAlignment = Alignment.Bottom, verticalAlignment = Alignment.Bottom,
) { ) {
if (enableVoiceMessages && voiceMessageState !is VoiceMessageState.Idle) { if (enableVoiceMessages && voiceMessageState !is VoiceMessageState.Idle) {
if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Sending) {
Box(
modifier = Modifier
.padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp)
.size(48.dp.applyScaleUp()),
contentAlignment = Alignment.Center,
) {
voiceDeleteButton()
}
} else {
Spacer(modifier = Modifier.width(16.dp))
}
Box( Box(
modifier = Modifier modifier = Modifier
.padding(start = 16.dp, bottom = 8.dp, top = 8.dp) .padding(bottom = 8.dp, top = 8.dp)
.weight(1f) .weight(1f)
) { ) {
voiceRecording() voiceRecording()

66
libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/components/VoiceMessageDeleteButton.kt

@ -0,0 +1,66 @@
/*
* 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.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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.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.utils.CommonDrawables
import io.element.android.libraries.theme.ElementTheme
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun VoiceMessageDeleteButton(
enabled: Boolean,
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
) {
IconButton(
modifier = modifier
.size(48.dp),
enabled = enabled,
onClick = onClick,
) {
Icon(
modifier = Modifier.size(24.dp.applyScaleUp()),
resourceId = CommonDrawables.ic_compound_delete,
contentDescription = stringResource(CommonStrings.a11y_delete),
tint = if (enabled) {
ElementTheme.colors.iconCriticalPrimary
} else {
ElementTheme.colors.iconDisabled
},
)
}
}
@PreviewsDayNight
@Composable
internal fun VoiceMessageDeleteButtonPreview() = ElementPreview {
Row {
VoiceMessageDeleteButton(enabled = true)
VoiceMessageDeleteButton(enabled = false)
}
}

1
libraries/voicerecorder/test/build.gradle.kts

@ -27,4 +27,5 @@ dependencies {
implementation(projects.tests.testutils) implementation(projects.tests.testutils)
implementation(libs.coroutines.test) implementation(libs.coroutines.test)
implementation(libs.test.truth)
} }

20
libraries/voicerecorder/test/src/main/kotlin/io/element/android/libraries/voicerecorder/test/FakeVoiceRecorder.kt

@ -16,6 +16,7 @@
package io.element.android.libraries.voicerecorder.test package io.element.android.libraries.voicerecorder.test
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.voicerecorder.api.VoiceRecorder import io.element.android.libraries.voicerecorder.api.VoiceRecorder
import io.element.android.libraries.voicerecorder.api.VoiceRecorderState import io.element.android.libraries.voicerecorder.api.VoiceRecorderState
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -37,7 +38,12 @@ class FakeVoiceRecorder(
private var securityException: SecurityException? = null private var securityException: SecurityException? = null
private var startedCount = 0
private var stoppedCount = 0
private var deletedCount = 0
override suspend fun startRecord() { override suspend fun startRecord() {
startedCount += 1
val startedAt = timeSource.markNow() val startedAt = timeSource.markNow()
securityException?.let { throw it } securityException?.let { throw it }
@ -55,6 +61,8 @@ class FakeVoiceRecorder(
override suspend fun stopRecord( override suspend fun stopRecord(
cancelled: Boolean cancelled: Boolean
) { ) {
stoppedCount++
if (cancelled) { if (cancelled) {
deleteRecording() deleteRecording()
} }
@ -68,6 +76,7 @@ class FakeVoiceRecorder(
} }
override suspend fun deleteRecording() { override suspend fun deleteRecording() {
deletedCount++
curRecording = null curRecording = null
_state.emit( _state.emit(
@ -75,6 +84,17 @@ class FakeVoiceRecorder(
) )
} }
fun assertCalls(
started: Int = 0,
stopped: Int = 0,
deleted: Int = 0,
) {
assertThat(startedCount).isEqualTo(started)
assertThat(stoppedCount).isEqualTo(stopped)
assertThat(deletedCount).isEqualTo(deleted)
}
fun givenThrowsSecurityException(exception: SecurityException) { fun givenThrowsSecurityException(exception: SecurityException) {
this.securityException = exception this.securityException = exception
} }

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageDeleteButton-D-14_14_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageDeleteButton-N-14_15_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

0
tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessagePreview-D-14_14_null,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessagePreview-D-15_15_null,NEXUS_5,1.0,en].png

0
tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessagePreview-N-14_15_null,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessagePreview-N-15_16_null,NEXUS_5,1.0,en].png

0
tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageRecording-D-15_15_null,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageRecording-D-16_16_null,NEXUS_5,1.0,en].png

0
tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageRecording-N-15_16_null,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessageRecording-N-16_17_null,NEXUS_5,1.0,en].png

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerVoice-D-4_4_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer_null_TextComposerVoice-N-4_5_null,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save