Merge branch 'develop' into dla/feature/custom_room_notification_settings_list
This commit is contained in:
commit
c61d2d3941
16 changed files with 173 additions and 10 deletions
|
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
||||||
|
|
@ -176,7 +178,7 @@ fun TextComposer(
|
||||||
}
|
}
|
||||||
|
|
||||||
val voiceRecording = @Composable {
|
val voiceRecording = @Composable {
|
||||||
when(voiceMessageState) {
|
when (voiceMessageState) {
|
||||||
VoiceMessageState.Preview ->
|
VoiceMessageState.Preview ->
|
||||||
VoiceMessagePreview(isInteractive = true)
|
VoiceMessagePreview(isInteractive = true)
|
||||||
VoiceMessageState.Sending ->
|
VoiceMessageState.Sending ->
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:1ad3be335771242252bbb2ce0e6944e1a53cb9e4bca81fd4566ccd79cf85ae95
|
||||||
|
size 5213
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:60f913b80137673f83c973b4e05de2b3303c478d85cbd8a8c9edc20e8fabdd45
|
||||||
|
size 5168
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:2c2eadc9585070e9a07dda78145c337a003166d4dc990e794d71ece0f8cab4f3
|
oid sha256:463aea9b16f9907af3acdc02d4c32e9f048b4a547e1079cd265e5138c759b461
|
||||||
size 17134
|
size 17917
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:8443556fa3ad8ca7c928689b3fdd2db43cec62ceb8db340353c90cbd7ea76355
|
oid sha256:240ae68a39f03df1cd46645d5fb5163c2fdb95ebe8083f8b18f72c118d5a2d25
|
||||||
size 16228
|
size 17030
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue