Skip to content

Commit 8c0d9cc

Browse files
jonnyandrewElementBot
and
ElementBot
authored
Add progress indicator for sending voice messages (#1618)
--------- Co-authored-by: ElementBot <[email protected]>
1 parent fc8d166 commit 8c0d9cc

File tree

31 files changed

+83
-18
lines changed

31 files changed

+83
-18
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@ class VoiceMessageComposerPresenter @Inject constructor(
142142
return VoiceMessageComposerState(
143143
voiceMessageState = when (val state = recorderState) {
144144
is VoiceRecorderState.Recording -> VoiceMessageState.Recording(level = state.level)
145-
is VoiceRecorderState.Finished -> VoiceMessageState.Preview
145+
is VoiceRecorderState.Finished -> if (isSending) {
146+
VoiceMessageState.Sending
147+
} else {
148+
VoiceMessageState.Preview
149+
}
146150
else -> VoiceMessageState.Idle
147151
},
148152
showPermissionRationaleDialog = permissionState.showDialog,

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class VoiceMessageComposerPresenterTest {
127127
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
128128
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
129129
awaitItem().eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
130+
assertThat(awaitItem().voiceMessageState).isEqualTo(VoiceMessageState.Sending)
130131

131132
val finalState = awaitItem()
132133
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
@@ -148,6 +149,7 @@ class VoiceMessageComposerPresenterTest {
148149
eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
149150
eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
150151
}
152+
assertThat(awaitItem().voiceMessageState).isEqualTo(VoiceMessageState.Sending)
151153

152154
val finalState = awaitItem()
153155
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
@@ -167,11 +169,13 @@ class VoiceMessageComposerPresenterTest {
167169
}.test {
168170
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.PressStart))
169171
awaitItem().eventSink(VoiceMessageComposerEvents.RecordButtonEvent(PressEvent.LongPressEnd))
170-
171-
val finalState = awaitItem().apply {
172+
awaitItem().apply {
172173
assertThat(voiceMessageState).isEqualTo(VoiceMessageState.Preview)
173174
eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
174175
}
176+
177+
val finalState = awaitItem()
178+
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Sending)
175179
assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
176180
assertThat(analyticsService.trackedErrors).hasSize(0)
177181

@@ -192,6 +196,7 @@ class VoiceMessageComposerPresenterTest {
192196
val previewState = awaitItem()
193197

194198
previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
199+
assertThat(awaitItem().voiceMessageState).isEqualTo(VoiceMessageState.Sending)
195200

196201
ensureAllEventsConsumed()
197202
assertThat(previewState.voiceMessageState).isEqualTo(VoiceMessageState.Preview)
@@ -349,7 +354,8 @@ class VoiceMessageComposerPresenterTest {
349354

350355
val onPauseState = when (mostRecentState.voiceMessageState) {
351356
VoiceMessageState.Idle,
352-
VoiceMessageState.Preview -> {
357+
VoiceMessageState.Preview,
358+
VoiceMessageState.Sending -> {
353359
mostRecentState
354360
}
355361
is VoiceMessageState.Recording -> {
@@ -364,7 +370,8 @@ class VoiceMessageComposerPresenterTest {
364370
)
365371

366372
when (onPauseState.voiceMessageState) {
367-
VoiceMessageState.Idle ->
373+
VoiceMessageState.Idle,
374+
VoiceMessageState.Sending ->
368375
ensureAllEventsConsumed()
369376
is VoiceMessageState.Recording,
370377
VoiceMessageState.Preview ->

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

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import androidx.compose.ui.unit.dp
5050
import io.element.android.libraries.designsystem.preview.ElementPreview
5151
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
5252
import io.element.android.libraries.designsystem.text.applyScaleUp
53+
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
5354
import io.element.android.libraries.designsystem.theme.components.Icon
5455
import io.element.android.libraries.designsystem.theme.components.Text
5556
import io.element.android.libraries.designsystem.utils.CommonDrawables
@@ -153,24 +154,35 @@ fun TextComposer(
153154
composerMode = composerMode,
154155
)
155156
}
157+
val uploadVoiceProgress = @Composable {
158+
CircularProgressIndicator(
159+
modifier = Modifier.size(24.dp),
160+
)
161+
}
156162

157163
val textFormattingOptions = @Composable { TextFormatting(state = state) }
158164

159165
val sendOrRecordButton = when {
160166
enableVoiceMessages && !canSendMessage ->
161167
when (voiceMessageState) {
168+
VoiceMessageState.Idle,
169+
is VoiceMessageState.Recording -> recordVoiceButton
162170
is VoiceMessageState.Preview -> sendVoiceButton
163-
else -> recordVoiceButton
171+
is VoiceMessageState.Sending -> uploadVoiceProgress
164172
}
165173
else ->
166174
sendButton
167175
}
168176

169177
val voiceRecording = @Composable {
170-
if (voiceMessageState is VoiceMessageState.Recording) {
171-
VoiceMessageRecording(voiceMessageState.level)
172-
} else if (voiceMessageState is VoiceMessageState.Preview) {
173-
VoiceMessagePreview()
178+
when(voiceMessageState) {
179+
VoiceMessageState.Preview ->
180+
VoiceMessagePreview(isInteractive = true)
181+
VoiceMessageState.Sending ->
182+
VoiceMessagePreview(isInteractive = false)
183+
is VoiceMessageState.Recording ->
184+
VoiceMessageRecording(voiceMessageState.level)
185+
VoiceMessageState.Idle -> {}
174186
}
175187
}
176188

@@ -245,6 +257,8 @@ private fun StandardLayout(
245257
Box(
246258
Modifier
247259
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
260+
.size(48.dp.applyScaleUp()),
261+
contentAlignment = Alignment.Center,
248262
) {
249263
endButton()
250264
}
@@ -721,6 +735,30 @@ internal fun TextComposerReplyPreview() = ElementPreview {
721735
)
722736
}
723737

738+
@PreviewsDayNight
739+
@Composable
740+
internal fun TextComposerVoicePreview() = ElementPreview {
741+
@Composable
742+
fun VoicePreview(
743+
voiceMessageState: VoiceMessageState
744+
) = TextComposer(
745+
RichTextEditorState("", initialFocus = true),
746+
voiceMessageState = voiceMessageState,
747+
onSendMessage = {},
748+
composerMode = MessageComposerMode.Normal,
749+
onResetComposerMode = {},
750+
enableTextFormatting = true,
751+
enableVoiceMessages = true,
752+
)
753+
PreviewColumn(items = persistentListOf({
754+
VoicePreview(voiceMessageState = VoiceMessageState.Recording(0.5))
755+
}, {
756+
VoicePreview(voiceMessageState = VoiceMessageState.Preview)
757+
}, {
758+
VoicePreview(voiceMessageState = VoiceMessageState.Sending)
759+
}))
760+
}
761+
724762
@Composable
725763
private fun PreviewColumn(
726764
items: ImmutableList<@Composable () -> Unit>,

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package io.element.android.libraries.textcomposer.components
1818

1919
import androidx.compose.foundation.background
20+
import androidx.compose.foundation.layout.Column
2021
import androidx.compose.foundation.layout.Row
2122
import androidx.compose.foundation.layout.fillMaxWidth
2223
import androidx.compose.foundation.layout.heightIn
@@ -33,6 +34,7 @@ import io.element.android.libraries.theme.ElementTheme
3334

3435
@Composable
3536
internal fun VoiceMessagePreview(
37+
isInteractive: Boolean,
3638
modifier: Modifier = Modifier,
3739
) {
3840
Row(
@@ -49,7 +51,11 @@ internal fun VoiceMessagePreview(
4951
// TODO Replace with recording preview UI
5052
Text(
5153
text = "Finished recording", // Not localized because it is a placeholder
52-
color = ElementTheme.colors.textSecondary,
54+
color = if (isInteractive) {
55+
ElementTheme.colors.textSecondary
56+
} else {
57+
ElementTheme.colors.textDisabled
58+
},
5359
style = ElementTheme.typography.fontBodySmMedium
5460
)
5561
}
@@ -58,5 +64,8 @@ internal fun VoiceMessagePreview(
5864
@PreviewsDayNight
5965
@Composable
6066
internal fun VoiceMessagePreviewPreview() = ElementPreview {
61-
VoiceMessagePreview()
67+
Column {
68+
VoiceMessagePreview(isInteractive = true)
69+
VoiceMessagePreview(isInteractive = false)
70+
}
6271
}

libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/model/VoiceMessageState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ sealed class VoiceMessageState {
2020
data object Idle: VoiceMessageState()
2121

2222
data object Preview: VoiceMessageState()
23+
data object Sending: VoiceMessageState()
2324
data class Recording(
2425
val level: Double,
2526
): VoiceMessageState()

tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessagePreview-D-13_13_null,NEXUS_5,1.0,en].png

Lines changed: 0 additions & 3 deletions
This file was deleted.
Loading

tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.components_null_VoiceMessagePreview-N-13_14_null,NEXUS_5,1.0,en].png

Lines changed: 0 additions & 3 deletions
This file was deleted.
Loading
Loading
Loading

0 commit comments

Comments
 (0)