Merge pull request #3943 from element-hq/feature/bma/_poc/mediaPreprocessing
Hide media preprocessing
This commit is contained in:
commit
b2a79d2dc8
11 changed files with 325 additions and 38 deletions
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.messages.impl.attachments.preview
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -19,10 +20,16 @@ import dagger.assisted.AssistedFactory
|
|||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
|
||||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
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.permalink.PermalinkBuilder
|
||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||
import io.element.android.libraries.mediaupload.api.allFiles
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
import io.element.android.libraries.textcomposer.model.rememberMarkdownTextEditorState
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
|
@ -39,6 +46,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
|||
private val mediaSender: MediaSender,
|
||||
private val permalinkBuilder: PermalinkBuilder,
|
||||
private val temporaryUriDeleter: TemporaryUriDeleter,
|
||||
private val featureFlagsService: FeatureFlagService,
|
||||
) : Presenter<AttachmentsPreviewState> {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
|
|
@ -63,19 +71,62 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
|||
|
||||
val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) }
|
||||
|
||||
val userSentAttachment = remember { mutableStateOf(false) }
|
||||
|
||||
val mediaUploadInfoState = remember { mutableStateOf<AsyncData<MediaUploadInfo>>(AsyncData.Uninitialized) }
|
||||
LaunchedEffect(Unit) {
|
||||
preProcessAttachment(
|
||||
attachment,
|
||||
mediaUploadInfoState,
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(userSentAttachment.value, mediaUploadInfoState.value) {
|
||||
if (userSentAttachment.value) {
|
||||
// User confirmed sending the attachment
|
||||
when (val mediaUploadInfo = mediaUploadInfoState.value) {
|
||||
is AsyncData.Success -> {
|
||||
// Pre-processing is done, send the attachment
|
||||
val caption = markdownTextEditorState.getMessageMarkdown(permalinkBuilder)
|
||||
.takeIf { it.isNotEmpty() }
|
||||
ongoingSendAttachmentJob.value = coroutineScope.launch {
|
||||
sendPreProcessedMedia(
|
||||
mediaUploadInfo = mediaUploadInfo.data,
|
||||
caption = caption,
|
||||
sendActionState = sendActionState,
|
||||
)
|
||||
}
|
||||
}
|
||||
is AsyncData.Failure -> {
|
||||
// Pre-processing has failed, show the error
|
||||
sendActionState.value = SendActionState.Failure(mediaUploadInfo.error)
|
||||
}
|
||||
AsyncData.Uninitialized,
|
||||
is AsyncData.Loading -> {
|
||||
// Pre-processing is still in progress, do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) {
|
||||
when (attachmentsPreviewEvents) {
|
||||
is AttachmentsPreviewEvents.SendAttachment -> {
|
||||
val caption = markdownTextEditorState.getMessageMarkdown(permalinkBuilder)
|
||||
.takeIf { it.isNotEmpty() }
|
||||
ongoingSendAttachmentJob.value = coroutineScope.sendAttachment(
|
||||
attachment = attachment,
|
||||
caption = caption,
|
||||
sendActionState = sendActionState,
|
||||
)
|
||||
is AttachmentsPreviewEvents.SendAttachment -> coroutineScope.launch {
|
||||
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
|
||||
userSentAttachment.value = true
|
||||
val instantSending = mediaUploadInfoState.value.isReady() && useSendQueue
|
||||
sendActionState.value = if (instantSending) {
|
||||
SendActionState.Sending.InstantSending
|
||||
} else {
|
||||
SendActionState.Sending.Processing
|
||||
}
|
||||
}
|
||||
AttachmentsPreviewEvents.Cancel -> {
|
||||
coroutineScope.cancel(attachment)
|
||||
coroutineScope.cancel(
|
||||
attachment,
|
||||
mediaUploadInfoState.value,
|
||||
sendActionState,
|
||||
)
|
||||
}
|
||||
AttachmentsPreviewEvents.ClearSendState -> {
|
||||
ongoingSendAttachmentJob.value?.let {
|
||||
|
|
@ -95,56 +146,95 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.sendAttachment(
|
||||
private fun CoroutineScope.preProcessAttachment(
|
||||
attachment: Attachment,
|
||||
caption: String?,
|
||||
sendActionState: MutableState<SendActionState>,
|
||||
mediaUploadInfoState: MutableState<AsyncData<MediaUploadInfo>>,
|
||||
) = launch {
|
||||
when (attachment) {
|
||||
is Attachment.Media -> {
|
||||
sendMedia(
|
||||
preProcessMedia(
|
||||
mediaAttachment = attachment,
|
||||
caption = caption,
|
||||
sendActionState = sendActionState,
|
||||
mediaUploadInfoState = mediaUploadInfoState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun preProcessMedia(
|
||||
mediaAttachment: Attachment.Media,
|
||||
mediaUploadInfoState: MutableState<AsyncData<MediaUploadInfo>>,
|
||||
) {
|
||||
mediaUploadInfoState.value = AsyncData.Loading()
|
||||
mediaSender.preProcessMedia(
|
||||
uri = mediaAttachment.localMedia.uri,
|
||||
mimeType = mediaAttachment.localMedia.info.mimeType,
|
||||
).fold(
|
||||
onSuccess = { mediaUploadInfo ->
|
||||
mediaUploadInfoState.value = AsyncData.Success(mediaUploadInfo)
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to pre-process media")
|
||||
if (it is CancellationException) {
|
||||
throw it
|
||||
} else {
|
||||
mediaUploadInfoState.value = AsyncData.Failure(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.cancel(
|
||||
attachment: Attachment,
|
||||
mediaUploadInfo: AsyncData<MediaUploadInfo>,
|
||||
sendActionState: MutableState<SendActionState>,
|
||||
) = launch {
|
||||
// Delete the temporary file
|
||||
when (attachment) {
|
||||
is Attachment.Media -> {
|
||||
temporaryUriDeleter.delete(attachment.localMedia.uri)
|
||||
mediaUploadInfo.dataOrNull()?.let { data ->
|
||||
cleanUp(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reset the sendActionState to ensure that dialog is closed before the screen
|
||||
sendActionState.value = SendActionState.Done
|
||||
onDoneListener()
|
||||
}
|
||||
|
||||
private suspend fun sendMedia(
|
||||
mediaAttachment: Attachment.Media,
|
||||
private fun cleanUp(
|
||||
mediaUploadInfo: MediaUploadInfo,
|
||||
) {
|
||||
mediaUploadInfo.allFiles().forEach { file ->
|
||||
file.safeDelete()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sendPreProcessedMedia(
|
||||
mediaUploadInfo: MediaUploadInfo,
|
||||
caption: String?,
|
||||
sendActionState: MutableState<SendActionState>,
|
||||
) = runCatching {
|
||||
val context = coroutineContext
|
||||
val progressCallback = object : ProgressCallback {
|
||||
override fun onProgress(current: Long, total: Long) {
|
||||
// Note will not happen if useSendQueue is true
|
||||
if (context.isActive) {
|
||||
sendActionState.value = SendActionState.Sending.Uploading(current.toFloat() / total.toFloat())
|
||||
}
|
||||
}
|
||||
}
|
||||
sendActionState.value = SendActionState.Sending.Processing
|
||||
mediaSender.sendMedia(
|
||||
uri = mediaAttachment.localMedia.uri,
|
||||
mimeType = mediaAttachment.localMedia.info.mimeType,
|
||||
mediaSender.sendPreProcessedMedia(
|
||||
mediaUploadInfo = mediaUploadInfo,
|
||||
caption = caption,
|
||||
formattedCaption = null,
|
||||
progressCallback = progressCallback
|
||||
).getOrThrow()
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
cleanUp(mediaUploadInfo)
|
||||
// Reset the sendActionState to ensure that dialog is closed before the screen
|
||||
sendActionState.value = SendActionState.Done
|
||||
onDoneListener()
|
||||
},
|
||||
onFailure = { error ->
|
||||
|
|
|
|||
|
|
@ -27,9 +27,11 @@ sealed interface SendActionState {
|
|||
|
||||
@Immutable
|
||||
sealed interface Sending : SendActionState {
|
||||
data object InstantSending : Sending
|
||||
data object Processing : Sending
|
||||
data class Uploading(val progress: Float) : Sending
|
||||
}
|
||||
|
||||
data class Failure(val error: Throwable) : SendActionState
|
||||
data object Done : SendActionState
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider<Attachment
|
|||
anAttachmentsPreviewState(mediaInfo = aVideoMediaInfo()),
|
||||
anAttachmentsPreviewState(mediaInfo = anAudioMediaInfo()),
|
||||
anAttachmentsPreviewState(mediaInfo = anApkMediaInfo()),
|
||||
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Processing),
|
||||
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f)),
|
||||
anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException("error"))),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -99,12 +99,17 @@ private fun AttachmentSendStateView(
|
|||
onRetryClick: () -> Unit
|
||||
) {
|
||||
when (sendActionState) {
|
||||
is SendActionState.Sending -> {
|
||||
is SendActionState.Sending.Processing -> {
|
||||
ProgressDialog(
|
||||
type = when (sendActionState) {
|
||||
is SendActionState.Sending.Uploading -> ProgressDialogType.Determinate(sendActionState.progress)
|
||||
SendActionState.Sending.Processing -> ProgressDialogType.Indeterminate
|
||||
},
|
||||
type = ProgressDialogType.Indeterminate,
|
||||
text = stringResource(id = CommonStrings.common_sending),
|
||||
showCancelButton = true,
|
||||
onDismissRequest = onDismissClick,
|
||||
)
|
||||
}
|
||||
is SendActionState.Sending.Uploading -> {
|
||||
ProgressDialog(
|
||||
type = ProgressDialogType.Determinate(sendActionState.progress),
|
||||
text = stringResource(id = CommonStrings.common_sending),
|
||||
showCancelButton = true,
|
||||
onDismissRequest = onDismissClick,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue