Media upload: branch progress callback to UI
This commit is contained in:
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Unit>>(Async.Uninitialized)
|
||||
mutableStateOf<SendActionState>(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<Async<Unit>>,
|
||||
sendActionState: MutableState<SendActionState>,
|
||||
) = launch {
|
||||
when (attachment) {
|
||||
is Attachment.Media -> {
|
||||
@@ -81,10 +80,26 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
||||
|
||||
private suspend fun sendMedia(
|
||||
mediaAttachment: Attachment.Media,
|
||||
sendActionState: MutableState<Async<Unit>>,
|
||||
sendActionState: MutableState<SendActionState>,
|
||||
) {
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Unit>,
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -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<AttachmentsPreviewState> {
|
||||
override val values: Sequence<AttachmentsPreviewState>
|
||||
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<Unit> = Async.Uninitialized) = AttachmentsPreviewState(
|
||||
sendActionState: SendActionState = SendActionState.Idle) = AttachmentsPreviewState(
|
||||
attachment = Attachment.Media(
|
||||
localMedia = LocalMedia("file://path".toUri(), mediaInfo),
|
||||
compressIfPossible = true
|
||||
|
||||
@@ -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<Unit>,
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<AttachmentsState>,
|
||||
) {
|
||||
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 {
|
||||
|
||||
@@ -38,5 +38,8 @@ data class MessageComposerState(
|
||||
sealed interface AttachmentsState {
|
||||
object None : AttachmentsState
|
||||
data class Previewing(val attachments: ImmutableList<Attachment>) : AttachmentsState
|
||||
data class Sending(val attachments: ImmutableList<Attachment>) : AttachmentsState
|
||||
sealed interface Sending : AttachmentsState {
|
||||
data class Processing(val attachments: ImmutableList<Attachment>) : Sending
|
||||
data class Uploading(val progress: Float) : Sending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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<Unit> {
|
||||
suspend fun sendMedia(
|
||||
uri: Uri,
|
||||
mimeType: String,
|
||||
compressIfPossible: Boolean,
|
||||
progressCallback: ProgressCallback? = null
|
||||
): Result<Unit> {
|
||||
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<Unit> {
|
||||
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"))
|
||||
|
||||
Reference in New Issue
Block a user