From f090aa021eed6f8f30c0071b035fba82bb7be41c Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 28 Jun 2023 23:14:08 +0200 Subject: [PATCH 1/6] Media upload: branch progress callback to UI --- .../features/messages/impl/MessagesView.kt | 11 ++++++- .../preview/AttachmentsPreviewPresenter.kt | 31 ++++++++++++++----- .../preview/AttachmentsPreviewState.kt | 15 +++++++-- .../AttachmentsPreviewStateProvider.kt | 8 ++--- .../preview/AttachmentsPreviewView.kt | 30 +++++++++++------- .../MessageComposerPresenter.kt | 12 +++++-- .../messagecomposer/MessageComposerState.kt | 5 ++- .../designsystem/components/ProgressDialog.kt | 30 ++++++++++++++++-- .../libraries/mediaupload/api/MediaSender.kt | 17 +++++++--- 9 files changed, 119 insertions(+), 40 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 0ea9caf29c..1582207a1c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -65,6 +65,7 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView import io.element.android.libraries.androidutils.ui.hideKeyboard import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.components.ProgressDialogType import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.button.BackButton @@ -196,7 +197,15 @@ private fun AttachmentStateView( is AttachmentsState.Previewing -> LaunchedEffect(state) { onPreviewAttachments(state.attachments) } - is AttachmentsState.Sending -> ProgressDialog(text = stringResource(id = CommonStrings.common_loading)) + is AttachmentsState.Sending -> { + ProgressDialog( + type = when (state) { + is AttachmentsState.Sending.Uploading -> ProgressDialogType.Determinate(state.progress) + is AttachmentsState.Sending.Processing -> ProgressDialogType.Indeterminate + }, + text = stringResource(id = CommonStrings.common_sending) + ) + } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index 9761a939b3..12ca7ca3f0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -25,9 +25,8 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.messages.impl.attachments.Attachment -import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.runUpdatingState +import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.mediaupload.api.MediaSender import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -48,13 +47,13 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( val coroutineScope = rememberCoroutineScope() val sendActionState = remember { - mutableStateOf>(Async.Uninitialized) + mutableStateOf(SendActionState.Idle) } fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) { when (attachmentsPreviewEvents) { AttachmentsPreviewEvents.SendAttachment -> coroutineScope.sendAttachment(attachment, sendActionState) - AttachmentsPreviewEvents.ClearSendState -> sendActionState.value = Async.Uninitialized + AttachmentsPreviewEvents.ClearSendState -> sendActionState.value = SendActionState.Idle } } @@ -67,7 +66,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( private fun CoroutineScope.sendAttachment( attachment: Attachment, - sendActionState: MutableState>, + sendActionState: MutableState, ) = launch { when (attachment) { is Attachment.Media -> { @@ -81,10 +80,26 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( private suspend fun sendMedia( mediaAttachment: Attachment.Media, - sendActionState: MutableState>, + sendActionState: MutableState, ) { - sendActionState.runUpdatingState { - mediaSender.sendMedia(mediaAttachment.localMedia.uri, mediaAttachment.localMedia.info.mimeType, mediaAttachment.compressIfPossible) + val progressCallback = object : ProgressCallback { + override fun onProgress(current: Long, total: Long) { + sendActionState.value = SendActionState.Sending.Uploading(current.toFloat() / total) + } } + sendActionState.value = SendActionState.Sending.Processing + mediaSender.sendMedia( + uri = mediaAttachment.localMedia.uri, + mimeType = mediaAttachment.localMedia.info.mimeType, + compressIfPossible = mediaAttachment.compressIfPossible, + progressCallback = progressCallback + ).fold( + onSuccess = { + sendActionState.value = SendActionState.Done + }, + onFailure = { + sendActionState.value = SendActionState.Failure(it) + } + ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt index 67350f5048..e41f43040f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewState.kt @@ -17,10 +17,21 @@ package io.element.android.features.messages.impl.attachments.preview import io.element.android.features.messages.impl.attachments.Attachment -import io.element.android.libraries.architecture.Async data class AttachmentsPreviewState( val attachment: Attachment, - val sendActionState: Async, + val sendActionState: SendActionState, val eventSink: (AttachmentsPreviewEvents) -> Unit ) + +sealed interface SendActionState { + object Idle : SendActionState + sealed interface Sending : SendActionState { + object Processing : Sending + data class Uploading(val progress: Float) : Sending + } + + data class Failure(val error: Throwable) : SendActionState + object Done : SendActionState +} + diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt index 58fea4a4f2..ee41ace4b0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt @@ -22,23 +22,21 @@ import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.features.messages.impl.media.local.MediaInfo import io.element.android.features.messages.impl.media.local.aFileInfo -import io.element.android.features.messages.impl.media.local.aVideoInfo import io.element.android.features.messages.impl.media.local.anImageInfo -import io.element.android.libraries.architecture.Async open class AttachmentsPreviewStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( anAttachmentsPreviewState(), anAttachmentsPreviewState(mediaInfo = aFileInfo()), - anAttachmentsPreviewState(sendActionState = Async.Loading()), - anAttachmentsPreviewState(sendActionState = Async.Failure(RuntimeException())), + anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f)), + anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException())), ) } fun anAttachmentsPreviewState( mediaInfo: MediaInfo = anImageInfo(), - sendActionState: Async = Async.Uninitialized) = AttachmentsPreviewState( + sendActionState: SendActionState = SendActionState.Idle) = AttachmentsPreviewState( attachment = Attachment.Media( localMedia = LocalMedia("file://path".toUri(), mediaInfo), compressIfPossible = true diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt index 7388dd665b..6f33ef5d0b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewView.kt @@ -33,9 +33,9 @@ import androidx.compose.ui.unit.dp import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError import io.element.android.features.messages.impl.media.local.LocalMediaView -import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.atomic.molecules.ButtonRowMolecule import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.components.ProgressDialogType import io.element.android.libraries.designsystem.components.dialogs.RetryDialog import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.theme.components.Scaffold @@ -58,7 +58,7 @@ fun AttachmentsPreviewView( state.eventSink(AttachmentsPreviewEvents.ClearSendState) } - if (state.sendActionState is Async.Success) { + if (state.sendActionState is SendActionState.Done) { LaunchedEffect(state.sendActionState) { onDismiss() } @@ -78,26 +78,32 @@ fun AttachmentsPreviewView( } AttachmentSendStateView( sendActionState = state.sendActionState, - onRetryClicked = ::postSendAttachment, - onRetryDismissed = ::postClearSendState + onDismissClicked = ::postClearSendState, + onRetryClicked = ::postSendAttachment ) } @Composable private fun AttachmentSendStateView( - sendActionState: Async, - onRetryDismissed: () -> Unit, + sendActionState: SendActionState, + onDismissClicked: () -> Unit, onRetryClicked: () -> Unit ) { - when (sendActionState) { - is Async.Loading -> { - ProgressDialog(text = stringResource(id = CommonStrings.common_loading)) - } - is Async.Failure -> { + when (sendActionState) { + is SendActionState.Sending -> { + ProgressDialog( + type = when (sendActionState) { + is SendActionState.Sending.Uploading -> ProgressDialogType.Determinate(sendActionState.progress) + SendActionState.Sending.Processing -> ProgressDialogType.Indeterminate + }, + text = stringResource(id = CommonStrings.common_sending) + ) + } + is SendActionState.Failure -> { RetryDialog( content = stringResource(sendAttachmentError(sendActionState.error)), - onDismiss = onRetryDismissed, + onDismiss = onDismissClicked, onRetry = onRetryClicked ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 39b003e0df..586977942b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -42,6 +42,7 @@ import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaSender @@ -107,7 +108,7 @@ class MessageComposerPresenter @Inject constructor( LaunchedEffect(attachmentsState.value) { when (val attachmentStateValue = attachmentsState.value) { - is AttachmentsState.Sending -> localCoroutineScope.sendAttachment(attachmentStateValue.attachments.first(), attachmentsState) + is AttachmentsState.Sending.Processing -> localCoroutineScope.sendAttachment(attachmentStateValue.attachments.first(), attachmentsState) else -> Unit } } @@ -238,7 +239,7 @@ class MessageComposerPresenter @Inject constructor( attachmentsState.value = if (isPreviewable) { AttachmentsState.Previewing(persistentListOf(mediaAttachment)) } else { - AttachmentsState.Sending(persistentListOf(mediaAttachment)) + AttachmentsState.Sending.Processing(persistentListOf(mediaAttachment)) } } @@ -247,7 +248,12 @@ class MessageComposerPresenter @Inject constructor( mimeType: String, attachmentState: MutableState, ) { - mediaSender.sendMedia(uri, mimeType, compressIfPossible = false) + val progressCallback = object : ProgressCallback { + override fun onProgress(current: Long, total: Long) { + attachmentState.value = AttachmentsState.Sending.Uploading(current.toFloat() / total) + } + } + mediaSender.sendMedia(uri, mimeType, compressIfPossible = false, progressCallback) .onSuccess { attachmentState.value = AttachmentsState.None }.onFailure { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt index 9c6a2c650a..fb4c3e82ba 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerState.kt @@ -38,5 +38,8 @@ data class MessageComposerState( sealed interface AttachmentsState { object None : AttachmentsState data class Previewing(val attachments: ImmutableList) : AttachmentsState - data class Sending(val attachments: ImmutableList) : AttachmentsState + sealed interface Sending : AttachmentsState { + data class Processing(val attachments: ImmutableList) : Sending + data class Uploading(val progress: Float) : Sending + } } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt index 35e6743881..99c2c2eacd 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt @@ -37,10 +37,16 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Text +sealed interface ProgressDialogType { + data class Determinate(val progress: Float) : ProgressDialogType + object Indeterminate : ProgressDialogType +} + @Composable fun ProgressDialog( modifier: Modifier = Modifier, text: String? = null, + type: ProgressDialogType = ProgressDialogType.Indeterminate, onDismiss: () -> Unit = {}, ) { Dialog( @@ -50,6 +56,21 @@ fun ProgressDialog( ProgressDialogContent( modifier = modifier, text = text, + progressIndicator = { + when (type) { + is ProgressDialogType.Indeterminate -> { + CircularProgressIndicator( + color = MaterialTheme.colorScheme.primary + ) + } + is ProgressDialogType.Determinate -> { + CircularProgressIndicator( + progress = type.progress, + color = MaterialTheme.colorScheme.primary + ) + } + } + } ) } } @@ -58,6 +79,11 @@ fun ProgressDialog( private fun ProgressDialogContent( modifier: Modifier = Modifier, text: String? = null, + progressIndicator: @Composable () -> Unit = { + CircularProgressIndicator( + color = MaterialTheme.colorScheme.primary + ) + } ) { Box( contentAlignment = Alignment.Center, @@ -71,9 +97,7 @@ private fun ProgressDialogContent( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(top = 38.dp, bottom = 32.dp, start = 40.dp, end = 40.dp) ) { - CircularProgressIndicator( - color = MaterialTheme.colorScheme.primary - ) + progressIndicator() if (!text.isNullOrBlank()) { Spacer(modifier = Modifier.height(22.dp)) Text( diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt index 9f27824858..6dab564b6e 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.mediaupload.api import android.net.Uri import io.element.android.libraries.core.extensions.flatMap +import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.room.MatrixRoom import javax.inject.Inject @@ -26,7 +27,12 @@ class MediaSender @Inject constructor( private val room: MatrixRoom, ) { - suspend fun sendMedia(uri: Uri, mimeType: String, compressIfPossible: Boolean): Result { + suspend fun sendMedia( + uri: Uri, + mimeType: String, + compressIfPossible: Boolean, + progressCallback: ProgressCallback? = null + ): Result { return preProcessor .process( uri = uri, @@ -35,12 +41,13 @@ class MediaSender @Inject constructor( compressIfPossible = compressIfPossible ) .flatMap { info -> - room.sendMedia(info) + room.sendMedia(info, progressCallback) } } private suspend fun MatrixRoom.sendMedia( info: MediaUploadInfo, + progressCallback: ProgressCallback? ): Result { return when (info) { is MediaUploadInfo.Image -> { @@ -48,7 +55,7 @@ class MediaSender @Inject constructor( file = info.file, thumbnailFile = info.thumbnailFile, imageInfo = info.info, - progressCallback = null + progressCallback = progressCallback ) } @@ -57,7 +64,7 @@ class MediaSender @Inject constructor( file = info.file, thumbnailFile = info.thumbnailFile, videoInfo = info.info, - progressCallback = null + progressCallback = progressCallback ) } @@ -65,7 +72,7 @@ class MediaSender @Inject constructor( sendFile( file = info.file, fileInfo = info.info, - progressCallback = null + progressCallback = progressCallback ) } else -> Result.failure(IllegalStateException("Unexpected MediaUploadInfo format: $info")) From 04eff17b8d21f14570cf3d91ba541427b501e657 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 29 Jun 2023 10:12:37 +0200 Subject: [PATCH 2/6] Upload media: fix test --- .../AttachmentsPreviewPresenterTest.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt index 18f5ae8da3..d7854f32b1 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt @@ -27,6 +27,7 @@ import io.element.android.features.messages.fixtures.aLocalMedia import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter +import io.element.android.features.messages.impl.attachments.preview.SendActionState import io.element.android.features.messages.impl.media.local.LocalMedia import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -52,12 +53,12 @@ class AttachmentsPreviewPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.sendActionState).isEqualTo(Async.Uninitialized) + assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) val loadingState = awaitItem() - assertThat(loadingState.sendActionState).isEqualTo(Async.Loading()) + assertThat(loadingState.sendActionState).isInstanceOf(SendActionState.Sending::class.java) val successState = awaitItem() - assertThat(successState.sendActionState).isEqualTo(Async.Success(Unit)) + assertThat(successState.sendActionState).isEqualTo(SendActionState.Done) assertThat(room.sendMediaCount).isEqualTo(1) } } @@ -72,16 +73,16 @@ class AttachmentsPreviewPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(initialState.sendActionState).isEqualTo(Async.Uninitialized) + assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) val loadingState = awaitItem() - assertThat(loadingState.sendActionState).isEqualTo(Async.Loading()) + assertThat(loadingState.sendActionState).isInstanceOf(SendActionState.Sending::class.java) val failureState = awaitItem() - assertThat(failureState.sendActionState).isEqualTo(Async.Failure(failure)) + assertThat(failureState.sendActionState).isEqualTo((SendActionState.Failure(failure))) assertThat(room.sendMediaCount).isEqualTo(0) failureState.eventSink(AttachmentsPreviewEvents.ClearSendState) val clearedState = awaitItem() - assertThat(clearedState.sendActionState).isEqualTo(Async.Uninitialized) + assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Idle) } } From b296937209616d237d95b03448dff0b4a3e8c1b1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 29 Jun 2023 10:41:59 +0200 Subject: [PATCH 3/6] Media upload : Fix lint --- .../designsystem/components/ProgressDialog.kt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt index 99c2c2eacd..140ab131ef 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/ProgressDialog.kt @@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview @@ -37,11 +38,6 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.Text -sealed interface ProgressDialogType { - data class Determinate(val progress: Float) : ProgressDialogType - object Indeterminate : ProgressDialogType -} - @Composable fun ProgressDialog( modifier: Modifier = Modifier, @@ -75,6 +71,12 @@ fun ProgressDialog( } } +@Immutable +sealed interface ProgressDialogType { + data class Determinate(val progress: Float) : ProgressDialogType + object Indeterminate : ProgressDialogType +} + @Composable private fun ProgressDialogContent( modifier: Modifier = Modifier, From 7dbfbf1aeb6f5b7356807dc5c67567b931d3e051 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 29 Jun 2023 09:27:48 +0000 Subject: [PATCH 4/6] Update screenshots --- ...AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png | 4 ++-- ...chmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 7af0fe0017..490e430dfe 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.analytics.impl_null_DefaultGroup_AnalyticsOptInViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c0354314eff5b0d893f34d4f32ae541e1688c09312b778505ab9a014d1a014a -size 50165 +oid sha256:4c40e8a33fa2a2ad0f261062d6e47b6a23ad3052c7ac1b5e6306570617e347d9 +size 50128 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.attachments.preview_null_DefaultGroup_AttachmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.attachments.preview_null_DefaultGroup_AttachmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index c20cd050c5..673685d3e8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.attachments.preview_null_DefaultGroup_AttachmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.attachments.preview_null_DefaultGroup_AttachmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b7d2036c810e634f6f40cc7db7abd743cc64ea91f583ccd225db93eedfbc0fd -size 185515 +oid sha256:c6baf3fd043fcb2d3249e09cfa9bf3a1ecb0aca92a4503135ba8cf5471dfd117 +size 184807 From ca58c997b5a018bb22430fccca8b8e760ac6c5a3 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Thu, 29 Jun 2023 11:04:08 +0000 Subject: [PATCH 5/6] Update screenshots --- ...chmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.attachments.preview_null_DefaultGroup_AttachmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.attachments.preview_null_DefaultGroup_AttachmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 673685d3e8..8840b9892a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.attachments.preview_null_DefaultGroup_AttachmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl.attachments.preview_null_DefaultGroup_AttachmentsPreviewViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6baf3fd043fcb2d3249e09cfa9bf3a1ecb0aca92a4503135ba8cf5471dfd117 -size 184807 +oid sha256:3a968039c2fa94e89c75e395bab808d2960c37c43576b558438ec17bcd17e507 +size 184797 From 2bd325620c9739af101872edde3ff93a0c8e0474 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 29 Jun 2023 17:40:19 +0200 Subject: [PATCH 6/6] Fix coverage and address PR review comment. --- .../preview/AttachmentsPreviewPresenter.kt | 2 +- .../MessageComposerPresenter.kt | 2 +- .../AttachmentsPreviewPresenterTest.kt | 16 +++++++++---- .../MessageComposerPresenterTest.kt | 12 +++++++++- .../matrix/test/room/FakeMatrixRoom.kt | 24 ++++++++++++++----- 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index 12ca7ca3f0..3ee87c0bc8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -84,7 +84,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( ) { val progressCallback = object : ProgressCallback { override fun onProgress(current: Long, total: Long) { - sendActionState.value = SendActionState.Sending.Uploading(current.toFloat() / total) + sendActionState.value = SendActionState.Sending.Uploading(current.toFloat() / total.toFloat()) } } sendActionState.value = SendActionState.Sending.Processing diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 586977942b..2878f8a8ab 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -250,7 +250,7 @@ class MessageComposerPresenter @Inject constructor( ) { val progressCallback = object : ProgressCallback { override fun onProgress(current: Long, total: Long) { - attachmentState.value = AttachmentsState.Sending.Uploading(current.toFloat() / total) + attachmentState.value = AttachmentsState.Sending.Uploading(current.toFloat() / total.toFloat()) } } mediaSender.sendMedia(uri, mimeType, compressIfPossible = false, progressCallback) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt index d7854f32b1..db202569ee 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/attachments/AttachmentsPreviewPresenterTest.kt @@ -29,7 +29,6 @@ import io.element.android.features.messages.impl.attachments.preview.Attachments import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter import io.element.android.features.messages.impl.attachments.preview.SendActionState import io.element.android.features.messages.impl.media.local.LocalMedia -import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaupload.api.MediaPreProcessor @@ -48,6 +47,13 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send media success scenario`() = runTest { val room = FakeMatrixRoom() + room.givenProgressCallbackValues( + listOf( + Pair(0, 10), + Pair(5, 10), + Pair(10, 10) + ) + ) val presenter = anAttachmentsPreviewPresenter(room = room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -55,8 +61,10 @@ class AttachmentsPreviewPresenterTest { val initialState = awaitItem() assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) - val loadingState = awaitItem() - assertThat(loadingState.sendActionState).isInstanceOf(SendActionState.Sending::class.java) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(0f)) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(0.5f)) + assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(1f)) val successState = awaitItem() assertThat(successState.sendActionState).isEqualTo(SendActionState.Done) assertThat(room.sendMediaCount).isEqualTo(1) @@ -76,7 +84,7 @@ class AttachmentsPreviewPresenterTest { assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) val loadingState = awaitItem() - assertThat(loadingState.sendActionState).isInstanceOf(SendActionState.Sending::class.java) + assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing) val failureState = awaitItem() assertThat(failureState.sendActionState).isEqualTo((SendActionState.Failure(failure))) assertThat(room.sendMediaCount).isEqualTo(0) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt index f4fc88f515..8e96820c47 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -368,6 +368,13 @@ class MessageComposerPresenterTest { @Test fun `present - Pick file from storage`() = runTest { val room = FakeMatrixRoom() + room.givenProgressCallbackValues( + listOf( + Pair(0, 10), + Pair(5, 10), + Pair(10, 10) + ) + ) val presenter = createPresenter(this, room = room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -376,7 +383,10 @@ class MessageComposerPresenterTest { initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) val sendingState = awaitItem() assertThat(sendingState.showAttachmentSourcePicker).isFalse() - assertThat(sendingState.attachmentsState).isInstanceOf(AttachmentsState.Sending::class.java) + assertThat(sendingState.attachmentsState).isInstanceOf(AttachmentsState.Sending.Processing::class.java) + assertThat(awaitItem().attachmentsState).isEqualTo(AttachmentsState.Sending.Uploading(0f)) + assertThat(awaitItem().attachmentsState).isEqualTo(AttachmentsState.Sending.Uploading(0.5f)) + assertThat(awaitItem().attachmentsState).isEqualTo(AttachmentsState.Sending.Uploading(1f)) val sentState = awaitItem() assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None) assertThat(room.sendMediaCount).isEqualTo(1) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 2555f68226..6326a83ebc 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import io.element.android.tests.testutils.simulateLongTask +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import java.io.File @@ -77,6 +78,7 @@ class FakeMatrixRoom( private var forwardEventResult = Result.success(Unit) private var reportContentResult = Result.success(Unit) private var sendLocationResult = Result.success(Unit) + private var progressCallbackValues = emptyList>() var sendMediaCount = 0 private set @@ -151,7 +153,7 @@ class FakeMatrixRoom( return toggleReactionResult } - if(_myReactions.contains(emoji)) { + if (_myReactions.contains(emoji)) { _myReactions.remove(emoji) } else { _myReactions.add(emoji) @@ -228,20 +230,26 @@ class FakeMatrixRoom( thumbnailFile: File, imageInfo: ImageInfo, progressCallback: ProgressCallback? - ): Result = fakeSendMedia() + ): Result = fakeSendMedia(progressCallback) - override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo, progressCallback: ProgressCallback?): Result = fakeSendMedia() + override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo, progressCallback: ProgressCallback?): Result = fakeSendMedia( + progressCallback + ) - override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result = fakeSendMedia() + override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result = fakeSendMedia(progressCallback) - override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result = fakeSendMedia() + override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result = fakeSendMedia(progressCallback) override suspend fun forwardEvent(eventId: EventId, rooms: List): Result = simulateLongTask { forwardEventResult } - private suspend fun fakeSendMedia(): Result = simulateLongTask { + private suspend fun fakeSendMedia(progressCallback: ProgressCallback?): Result = simulateLongTask { sendMediaResult.onSuccess { + progressCallbackValues.forEach { (current, total) -> + progressCallback?.onProgress(current, total) + delay(1) + } sendMediaCount++ } } @@ -380,4 +388,8 @@ class FakeMatrixRoom( fun givenSendLocationResult(result: Result) { sendLocationResult = result } + + fun givenProgressCallbackValues(values: List>) { + progressCallbackValues = values + } }