Merge pull request #721 from vector-im/feature/fga/media_upload_progress

Feature/fga/media upload progress
This commit is contained in:
ganfra 2023-06-29 18:01:50 +02:00 committed by GitHub
commit e4aae094c2
13 changed files with 170 additions and 58 deletions

View file

@ -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.features.networkmonitor.api.ui.ConnectivityIndicatorView
import io.element.android.libraries.androidutils.ui.hideKeyboard import io.element.android.libraries.androidutils.ui.hideKeyboard
import io.element.android.libraries.designsystem.components.ProgressDialog 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.Avatar
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
@ -216,7 +217,15 @@ private fun AttachmentStateView(
is AttachmentsState.Previewing -> LaunchedEffect(state) { is AttachmentsState.Previewing -> LaunchedEffect(state) {
onPreviewAttachments(state.attachments) 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)
)
}
} }
} }

View file

@ -25,9 +25,8 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.messages.impl.attachments.Attachment 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.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 io.element.android.libraries.mediaupload.api.MediaSender
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -48,13 +47,13 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val sendActionState = remember { val sendActionState = remember {
mutableStateOf<Async<Unit>>(Async.Uninitialized) mutableStateOf<SendActionState>(SendActionState.Idle)
} }
fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) { fun handleEvents(attachmentsPreviewEvents: AttachmentsPreviewEvents) {
when (attachmentsPreviewEvents) { when (attachmentsPreviewEvents) {
AttachmentsPreviewEvents.SendAttachment -> coroutineScope.sendAttachment(attachment, sendActionState) 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( private fun CoroutineScope.sendAttachment(
attachment: Attachment, attachment: Attachment,
sendActionState: MutableState<Async<Unit>>, sendActionState: MutableState<SendActionState>,
) = launch { ) = launch {
when (attachment) { when (attachment) {
is Attachment.Media -> { is Attachment.Media -> {
@ -81,10 +80,26 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
private suspend fun sendMedia( private suspend fun sendMedia(
mediaAttachment: Attachment.Media, mediaAttachment: Attachment.Media,
sendActionState: MutableState<Async<Unit>>, sendActionState: MutableState<SendActionState>,
) { ) {
sendActionState.runUpdatingState { val progressCallback = object : ProgressCallback {
mediaSender.sendMedia(mediaAttachment.localMedia.uri, mediaAttachment.localMedia.info.mimeType, mediaAttachment.compressIfPossible) override fun onProgress(current: Long, total: Long) {
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,
compressIfPossible = mediaAttachment.compressIfPossible,
progressCallback = progressCallback
).fold(
onSuccess = {
sendActionState.value = SendActionState.Done
},
onFailure = {
sendActionState.value = SendActionState.Failure(it)
}
)
} }
} }

View file

@ -17,10 +17,21 @@
package io.element.android.features.messages.impl.attachments.preview package io.element.android.features.messages.impl.attachments.preview
import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.libraries.architecture.Async
data class AttachmentsPreviewState( data class AttachmentsPreviewState(
val attachment: Attachment, val attachment: Attachment,
val sendActionState: Async<Unit>, val sendActionState: SendActionState,
val eventSink: (AttachmentsPreviewEvents) -> Unit 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
}

View file

@ -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.LocalMedia
import io.element.android.features.messages.impl.media.local.MediaInfo 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.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.features.messages.impl.media.local.anImageInfo
import io.element.android.libraries.architecture.Async
open class AttachmentsPreviewStateProvider : PreviewParameterProvider<AttachmentsPreviewState> { open class AttachmentsPreviewStateProvider : PreviewParameterProvider<AttachmentsPreviewState> {
override val values: Sequence<AttachmentsPreviewState> override val values: Sequence<AttachmentsPreviewState>
get() = sequenceOf( get() = sequenceOf(
anAttachmentsPreviewState(), anAttachmentsPreviewState(),
anAttachmentsPreviewState(mediaInfo = aFileInfo()), anAttachmentsPreviewState(mediaInfo = aFileInfo()),
anAttachmentsPreviewState(sendActionState = Async.Loading()), anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f)),
anAttachmentsPreviewState(sendActionState = Async.Failure(RuntimeException())), anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException())),
) )
} }
fun anAttachmentsPreviewState( fun anAttachmentsPreviewState(
mediaInfo: MediaInfo = anImageInfo(), mediaInfo: MediaInfo = anImageInfo(),
sendActionState: Async<Unit> = Async.Uninitialized) = AttachmentsPreviewState( sendActionState: SendActionState = SendActionState.Idle) = AttachmentsPreviewState(
attachment = Attachment.Media( attachment = Attachment.Media(
localMedia = LocalMedia("file://path".toUri(), mediaInfo), localMedia = LocalMedia("file://path".toUri(), mediaInfo),
compressIfPossible = true compressIfPossible = true

View file

@ -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.Attachment
import io.element.android.features.messages.impl.attachments.preview.error.sendAttachmentError 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.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.atomic.molecules.ButtonRowMolecule
import io.element.android.libraries.designsystem.components.ProgressDialog 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.components.dialogs.RetryDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Scaffold
@ -58,7 +58,7 @@ fun AttachmentsPreviewView(
state.eventSink(AttachmentsPreviewEvents.ClearSendState) state.eventSink(AttachmentsPreviewEvents.ClearSendState)
} }
if (state.sendActionState is Async.Success) { if (state.sendActionState is SendActionState.Done) {
LaunchedEffect(state.sendActionState) { LaunchedEffect(state.sendActionState) {
onDismiss() onDismiss()
} }
@ -78,26 +78,32 @@ fun AttachmentsPreviewView(
} }
AttachmentSendStateView( AttachmentSendStateView(
sendActionState = state.sendActionState, sendActionState = state.sendActionState,
onRetryClicked = ::postSendAttachment, onDismissClicked = ::postClearSendState,
onRetryDismissed = ::postClearSendState onRetryClicked = ::postSendAttachment
) )
} }
@Composable @Composable
private fun AttachmentSendStateView( private fun AttachmentSendStateView(
sendActionState: Async<Unit>, sendActionState: SendActionState,
onRetryDismissed: () -> Unit, onDismissClicked: () -> Unit,
onRetryClicked: () -> 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( RetryDialog(
content = stringResource(sendAttachmentError(sendActionState.error)), content = stringResource(sendAttachmentError(sendActionState.error)),
onDismiss = onRetryDismissed, onDismiss = onDismissClicked,
onRetry = onRetryClicked onRetry = onRetryClicked
) )
} }

View file

@ -42,6 +42,7 @@ import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags 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.matrix.api.room.MatrixRoom
import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaSender
@ -110,7 +111,7 @@ class MessageComposerPresenter @Inject constructor(
LaunchedEffect(attachmentsState.value) { LaunchedEffect(attachmentsState.value) {
when (val attachmentStateValue = 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 else -> Unit
} }
} }
@ -245,7 +246,7 @@ class MessageComposerPresenter @Inject constructor(
attachmentsState.value = if (isPreviewable) { attachmentsState.value = if (isPreviewable) {
AttachmentsState.Previewing(persistentListOf(mediaAttachment)) AttachmentsState.Previewing(persistentListOf(mediaAttachment))
} else { } else {
AttachmentsState.Sending(persistentListOf(mediaAttachment)) AttachmentsState.Sending.Processing(persistentListOf(mediaAttachment))
} }
} }
@ -254,7 +255,12 @@ class MessageComposerPresenter @Inject constructor(
mimeType: String, mimeType: String,
attachmentState: MutableState<AttachmentsState>, 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.toFloat())
}
}
mediaSender.sendMedia(uri, mimeType, compressIfPossible = false, progressCallback)
.onSuccess { .onSuccess {
attachmentState.value = AttachmentsState.None attachmentState.value = AttachmentsState.None
}.onFailure { }.onFailure {

View file

@ -39,5 +39,8 @@ data class MessageComposerState(
sealed interface AttachmentsState { sealed interface AttachmentsState {
object None : AttachmentsState object None : AttachmentsState
data class Previewing(val attachments: ImmutableList<Attachment>) : 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
}
} }

View file

@ -27,8 +27,8 @@ 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.Attachment
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents 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.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.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.api.room.MatrixRoom
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaPreProcessor
@ -47,17 +47,26 @@ class AttachmentsPreviewPresenterTest {
@Test @Test
fun `present - send media success scenario`() = runTest { fun `present - send media success scenario`() = runTest {
val room = FakeMatrixRoom() val room = FakeMatrixRoom()
room.givenProgressCallbackValues(
listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
)
)
val presenter = anAttachmentsPreviewPresenter(room = room) val presenter = anAttachmentsPreviewPresenter(room = room)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(Async.Uninitialized) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
val loadingState = awaitItem() assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing)
assertThat(loadingState.sendActionState).isEqualTo(Async.Loading<Unit>()) 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() val successState = awaitItem()
assertThat(successState.sendActionState).isEqualTo(Async.Success(Unit)) assertThat(successState.sendActionState).isEqualTo(SendActionState.Done)
assertThat(room.sendMediaCount).isEqualTo(1) assertThat(room.sendMediaCount).isEqualTo(1)
} }
} }
@ -72,16 +81,16 @@ class AttachmentsPreviewPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(Async.Uninitialized) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
val loadingState = awaitItem() val loadingState = awaitItem()
assertThat(loadingState.sendActionState).isEqualTo(Async.Loading<Unit>()) assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing)
val failureState = awaitItem() val failureState = awaitItem()
assertThat(failureState.sendActionState).isEqualTo(Async.Failure<Unit>(failure)) assertThat(failureState.sendActionState).isEqualTo((SendActionState.Failure(failure)))
assertThat(room.sendMediaCount).isEqualTo(0) assertThat(room.sendMediaCount).isEqualTo(0)
failureState.eventSink(AttachmentsPreviewEvents.ClearSendState) failureState.eventSink(AttachmentsPreviewEvents.ClearSendState)
val clearedState = awaitItem() val clearedState = awaitItem()
assertThat(clearedState.sendActionState).isEqualTo(Async.Uninitialized) assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Idle)
} }
} }

View file

@ -368,6 +368,13 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - Pick file from storage`() = runTest { fun `present - Pick file from storage`() = runTest {
val room = FakeMatrixRoom() val room = FakeMatrixRoom()
room.givenProgressCallbackValues(
listOf(
Pair(0, 10),
Pair(5, 10),
Pair(10, 10)
)
)
val presenter = createPresenter(this, room = room) val presenter = createPresenter(this, room = room)
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
@ -376,7 +383,10 @@ class MessageComposerPresenterTest {
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles)
val sendingState = awaitItem() val sendingState = awaitItem()
assertThat(sendingState.showAttachmentSourcePicker).isFalse() 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() val sentState = awaitItem()
assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None) assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None)
assertThat(room.sendMediaCount).isEqualTo(1) assertThat(room.sendMediaCount).isEqualTo(1)

View file

@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -41,6 +42,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
fun ProgressDialog( fun ProgressDialog(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
text: String? = null, text: String? = null,
type: ProgressDialogType = ProgressDialogType.Indeterminate,
onDismiss: () -> Unit = {}, onDismiss: () -> Unit = {},
) { ) {
Dialog( Dialog(
@ -50,14 +52,40 @@ fun ProgressDialog(
ProgressDialogContent( ProgressDialogContent(
modifier = modifier, modifier = modifier,
text = text, text = text,
progressIndicator = {
when (type) {
is ProgressDialogType.Indeterminate -> {
CircularProgressIndicator(
color = MaterialTheme.colorScheme.primary
)
}
is ProgressDialogType.Determinate -> {
CircularProgressIndicator(
progress = type.progress,
color = MaterialTheme.colorScheme.primary
)
}
}
}
) )
} }
} }
@Immutable
sealed interface ProgressDialogType {
data class Determinate(val progress: Float) : ProgressDialogType
object Indeterminate : ProgressDialogType
}
@Composable @Composable
private fun ProgressDialogContent( private fun ProgressDialogContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
text: String? = null, text: String? = null,
progressIndicator: @Composable () -> Unit = {
CircularProgressIndicator(
color = MaterialTheme.colorScheme.primary
)
}
) { ) {
Box( Box(
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
@ -71,9 +99,7 @@ private fun ProgressDialogContent(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(top = 38.dp, bottom = 32.dp, start = 40.dp, end = 40.dp) modifier = Modifier.padding(top = 38.dp, bottom = 32.dp, start = 40.dp, end = 40.dp)
) { ) {
CircularProgressIndicator( progressIndicator()
color = MaterialTheme.colorScheme.primary
)
if (!text.isNullOrBlank()) { if (!text.isNullOrBlank()) {
Spacer(modifier = Modifier.height(22.dp)) Spacer(modifier = Modifier.height(22.dp))
Text( Text(

View file

@ -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.A_SESSION_ID
import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline
import io.element.android.tests.testutils.simulateLongTask import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.io.File import java.io.File
@ -78,6 +79,7 @@ class FakeMatrixRoom(
private var forwardEventResult = Result.success(Unit) private var forwardEventResult = Result.success(Unit)
private var reportContentResult = Result.success(Unit) private var reportContentResult = Result.success(Unit)
private var sendLocationResult = Result.success(Unit) private var sendLocationResult = Result.success(Unit)
private var progressCallbackValues = emptyList<Pair<Long, Long>>()
var sendMediaCount = 0 var sendMediaCount = 0
private set private set
@ -152,7 +154,7 @@ class FakeMatrixRoom(
return toggleReactionResult return toggleReactionResult
} }
if(_myReactions.contains(emoji)) { if (_myReactions.contains(emoji)) {
_myReactions.remove(emoji) _myReactions.remove(emoji)
} else { } else {
_myReactions.add(emoji) _myReactions.add(emoji)
@ -229,20 +231,26 @@ class FakeMatrixRoom(
thumbnailFile: File, thumbnailFile: File,
imageInfo: ImageInfo, imageInfo: ImageInfo,
progressCallback: ProgressCallback? progressCallback: ProgressCallback?
): Result<Unit> = fakeSendMedia() ): Result<Unit> = fakeSendMedia(progressCallback)
override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo, progressCallback: ProgressCallback?): Result<Unit> = fakeSendMedia() override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo, progressCallback: ProgressCallback?): Result<Unit> = fakeSendMedia(
progressCallback
)
override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<Unit> = fakeSendMedia() override suspend fun sendAudio(file: File, audioInfo: AudioInfo, progressCallback: ProgressCallback?): Result<Unit> = fakeSendMedia(progressCallback)
override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<Unit> = fakeSendMedia() override suspend fun sendFile(file: File, fileInfo: FileInfo, progressCallback: ProgressCallback?): Result<Unit> = fakeSendMedia(progressCallback)
override suspend fun forwardEvent(eventId: EventId, rooms: List<RoomId>): Result<Unit> = simulateLongTask { override suspend fun forwardEvent(eventId: EventId, rooms: List<RoomId>): Result<Unit> = simulateLongTask {
forwardEventResult forwardEventResult
} }
private suspend fun fakeSendMedia(): Result<Unit> = simulateLongTask { private suspend fun fakeSendMedia(progressCallback: ProgressCallback?): Result<Unit> = simulateLongTask {
sendMediaResult.onSuccess { sendMediaResult.onSuccess {
progressCallbackValues.forEach { (current, total) ->
progressCallback?.onProgress(current, total)
delay(1)
}
sendMediaCount++ sendMediaCount++
} }
} }
@ -381,4 +389,8 @@ class FakeMatrixRoom(
fun givenSendLocationResult(result: Result<Unit>) { fun givenSendLocationResult(result: Result<Unit>) {
sendLocationResult = result sendLocationResult = result
} }
fun givenProgressCallbackValues(values: List<Pair<Long, Long>>) {
progressCallbackValues = values
}
} }

View file

@ -18,6 +18,7 @@ package io.element.android.libraries.mediaupload.api
import android.net.Uri import android.net.Uri
import io.element.android.libraries.core.extensions.flatMap 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 io.element.android.libraries.matrix.api.room.MatrixRoom
import javax.inject.Inject import javax.inject.Inject
@ -26,7 +27,12 @@ class MediaSender @Inject constructor(
private val room: MatrixRoom, 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 return preProcessor
.process( .process(
uri = uri, uri = uri,
@ -35,12 +41,13 @@ class MediaSender @Inject constructor(
compressIfPossible = compressIfPossible compressIfPossible = compressIfPossible
) )
.flatMap { info -> .flatMap { info ->
room.sendMedia(info) room.sendMedia(info, progressCallback)
} }
} }
private suspend fun MatrixRoom.sendMedia( private suspend fun MatrixRoom.sendMedia(
info: MediaUploadInfo, info: MediaUploadInfo,
progressCallback: ProgressCallback?
): Result<Unit> { ): Result<Unit> {
return when (info) { return when (info) {
is MediaUploadInfo.Image -> { is MediaUploadInfo.Image -> {
@ -48,7 +55,7 @@ class MediaSender @Inject constructor(
file = info.file, file = info.file,
thumbnailFile = info.thumbnailFile, thumbnailFile = info.thumbnailFile,
imageInfo = info.info, imageInfo = info.info,
progressCallback = null progressCallback = progressCallback
) )
} }
@ -57,7 +64,7 @@ class MediaSender @Inject constructor(
file = info.file, file = info.file,
thumbnailFile = info.thumbnailFile, thumbnailFile = info.thumbnailFile,
videoInfo = info.info, videoInfo = info.info,
progressCallback = null progressCallback = progressCallback
) )
} }
@ -65,7 +72,7 @@ class MediaSender @Inject constructor(
sendFile( sendFile(
file = info.file, file = info.file,
fileInfo = info.info, fileInfo = info.info,
progressCallback = null progressCallback = progressCallback
) )
} }
else -> Result.failure(IllegalStateException("Unexpected MediaUploadInfo format: $info")) else -> Result.failure(IllegalStateException("Unexpected MediaUploadInfo format: $info"))

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:99720bb9fe43eed7901c4e093da7a82000ea6721041095486719283fdec2eb52 oid sha256:3a968039c2fa94e89c75e395bab808d2960c37c43576b558438ec17bcd17e507
size 185514 size 184797