mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
[Gallery] Set media optimation user choice per media
This commit is contained in:
+93
-59
@@ -28,6 +28,8 @@ import io.element.android.features.messages.impl.attachments.preview.imageeditor
|
|||||||
import io.element.android.features.messages.impl.attachments.preview.imageeditor.AttachmentImageEditorState
|
import io.element.android.features.messages.impl.attachments.preview.imageeditor.AttachmentImageEditorState
|
||||||
import io.element.android.features.messages.impl.attachments.preview.imageeditor.AttachmentImageEdits
|
import io.element.android.features.messages.impl.attachments.preview.imageeditor.AttachmentImageEdits
|
||||||
import io.element.android.features.messages.impl.attachments.video.MediaOptimizationSelectorPresenter
|
import io.element.android.features.messages.impl.attachments.video.MediaOptimizationSelectorPresenter
|
||||||
|
import io.element.android.features.messages.impl.attachments.video.MediaOptimizationSelectorState
|
||||||
|
import io.element.android.features.messages.impl.attachments.video.VideoCompressionPresetSelector
|
||||||
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
|
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
|
||||||
import io.element.android.libraries.androidutils.file.safeDelete
|
import io.element.android.libraries.androidutils.file.safeDelete
|
||||||
import io.element.android.libraries.androidutils.hash.hash
|
import io.element.android.libraries.androidutils.hash.hash
|
||||||
@@ -69,6 +71,7 @@ class AttachmentsPreviewPresenter(
|
|||||||
private val temporaryUriDeleter: TemporaryUriDeleter,
|
private val temporaryUriDeleter: TemporaryUriDeleter,
|
||||||
private val attachmentImageEditor: AttachmentImageEditor,
|
private val attachmentImageEditor: AttachmentImageEditor,
|
||||||
private val mediaOptimizationSelectorPresenterFactory: MediaOptimizationSelectorPresenter.Factory,
|
private val mediaOptimizationSelectorPresenterFactory: MediaOptimizationSelectorPresenter.Factory,
|
||||||
|
private val videoCompressionPresetSelector: VideoCompressionPresetSelector,
|
||||||
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
|
||||||
private val dispatchers: CoroutineDispatchers,
|
private val dispatchers: CoroutineDispatchers,
|
||||||
private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider,
|
private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider,
|
||||||
@@ -97,9 +100,7 @@ class AttachmentsPreviewPresenter(
|
|||||||
val sendActionState = remember {
|
val sendActionState = remember {
|
||||||
mutableStateOf<SendActionState>(SendActionState.Idle)
|
mutableStateOf<SendActionState>(SendActionState.Idle)
|
||||||
}
|
}
|
||||||
val originalLocalMedia = remember { (attachments.first() as Attachment.Media).localMedia }
|
var canEditImage by remember { mutableStateOf(false) }
|
||||||
var currentAttachment by remember { mutableStateOf(attachments.first()) }
|
|
||||||
var canEditImage by remember { mutableStateOf(originalLocalMedia.info.canEditImage()) }
|
|
||||||
var imageEditorState by remember { mutableStateOf<AttachmentImageEditorState?>(null) }
|
var imageEditorState by remember { mutableStateOf<AttachmentImageEditorState?>(null) }
|
||||||
var isApplyingImageEdits by remember { mutableStateOf(false) }
|
var isApplyingImageEdits by remember { mutableStateOf(false) }
|
||||||
var displayImageEditError by remember { mutableStateOf(false) }
|
var displayImageEditError by remember { mutableStateOf(false) }
|
||||||
@@ -130,37 +131,57 @@ class AttachmentsPreviewPresenter(
|
|||||||
|
|
||||||
var preprocessMediaJob by remember { mutableStateOf<Job?>(null) }
|
var preprocessMediaJob by remember { mutableStateOf<Job?>(null) }
|
||||||
|
|
||||||
val firstMediaAttachment = attachments.first() as Attachment.Media
|
val mediaOptimizationSelectorPresenters = remember {
|
||||||
val mediaOptimizationSelectorPresenter = remember {
|
attachments
|
||||||
mediaOptimizationSelectorPresenterFactory.create(
|
.filterIsInstance<Attachment.Media>()
|
||||||
localMedia = firstMediaAttachment.localMedia,
|
.mapIndexed { index, attachment ->
|
||||||
sendAsFile = firstMediaAttachment.sendAsFile,
|
mediaOptimizationSelectorPresenterFactory.create(
|
||||||
)
|
index = index,
|
||||||
|
localMedia = attachment.localMedia,
|
||||||
|
sendAsFile = attachment.sendAsFile,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val mediaOptimizationSelectorState by rememberUpdatedState(mediaOptimizationSelectorPresenter.present())
|
val mediaOptimizationSelectorStates by rememberUpdatedState(
|
||||||
|
mediaOptimizationSelectorPresenters.map {
|
||||||
|
it.present()
|
||||||
|
}.toImmutableList()
|
||||||
|
)
|
||||||
|
|
||||||
val observableSendState = snapshotFlow { sendActionState.value }
|
val observableSendState = snapshotFlow { sendActionState.value }
|
||||||
|
|
||||||
var displayFileTooLargeError by remember { mutableStateOf(false) }
|
var displayFileTooLargeError by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(mediaOptimizationSelectorState.displayMediaSelectorViews, mediaOptimizationSelectorState.selectedVideoPreset) {
|
LaunchedEffect(
|
||||||
if (mediaOptimizationSelectorState.displayMediaSelectorViews == false &&
|
mediaOptimizationSelectorStates,
|
||||||
imageEditorState == null &&
|
imageEditorState,
|
||||||
!isApplyingImageEdits
|
isApplyingImageEdits,
|
||||||
|
editedAttachments,
|
||||||
|
) {
|
||||||
|
if (mediaOptimizationSelectorStates.any { it.displayMediaSelectorViews == true } ||
|
||||||
|
imageEditorState != null ||
|
||||||
|
isApplyingImageEdits
|
||||||
) {
|
) {
|
||||||
val config = MediaOptimizationConfig(
|
// If any of the media optimization selectors are displayed, we don't want to pre-process the media yet
|
||||||
compressImages = mediaOptimizationSelectorState.isImageOptimizationEnabled ?: mediaOptimizationConfigProvider.get().compressImages,
|
return@LaunchedEffect
|
||||||
videoCompressionPreset = mediaOptimizationSelectorState.selectedVideoPreset ?: mediaOptimizationConfigProvider.get().videoCompressionPreset,
|
}
|
||||||
|
// If the media optimization selector is not displayed, we can pre-process the media
|
||||||
|
// to prepare it for sending. This is done to avoid blocking the UI thread when the
|
||||||
|
// user clicks on the send button.
|
||||||
|
val configs = mediaOptimizationSelectorStates.mapIndexed { index, mediaOptimizationSelectorState ->
|
||||||
|
getAutoPreprocessMediaOptimizationConfig(
|
||||||
|
mediaAttachment = editedAttachments[index] as Attachment.Media,
|
||||||
|
mediaOptimizationSelectorState = mediaOptimizationSelectorState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
preprocessMediaJob?.cancel()
|
||||||
|
preprocessMediaJob = coroutineScope.launch(dispatchers.io) {
|
||||||
|
preProcessAttachments(
|
||||||
|
attachments = editedAttachments,
|
||||||
|
mediaOptimizationConfigs = configs,
|
||||||
|
displayProgress = false,
|
||||||
|
sendActionState = sendActionState,
|
||||||
)
|
)
|
||||||
preprocessMediaJob?.cancel()
|
|
||||||
preprocessMediaJob = coroutineScope.launch(dispatchers.io) {
|
|
||||||
preProcessAttachments(
|
|
||||||
attachments = editedAttachments,
|
|
||||||
mediaOptimizationConfig = config,
|
|
||||||
displayProgress = false,
|
|
||||||
sendActionState = sendActionState,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +192,9 @@ class AttachmentsPreviewPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val maxUploadSize = mediaOptimizationSelectorState.maxUploadSize.dataOrNull()
|
val maxUploadSize = mediaOptimizationSelectorStates.firstNotNullOfOrNull {
|
||||||
|
it.maxUploadSize.dataOrNull()
|
||||||
|
}
|
||||||
LaunchedEffect(maxUploadSize) {
|
LaunchedEffect(maxUploadSize) {
|
||||||
if (maxUploadSize != null) {
|
if (maxUploadSize != null) {
|
||||||
// If file size is not known, we're permissive and allow sending. The SDK will cancel the upload if needed.
|
// If file size is not known, we're permissive and allow sending. The SDK will cancel the upload if needed.
|
||||||
@@ -192,11 +215,13 @@ class AttachmentsPreviewPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val videoSizeEstimations = mediaOptimizationSelectorState.videoSizeEstimations.dataOrNull()
|
mediaOptimizationSelectorStates.forEach { mediaOptimizationSelectorState ->
|
||||||
LaunchedEffect(videoSizeEstimations) {
|
val videoSizeEstimations = mediaOptimizationSelectorState.videoSizeEstimations.dataOrNull()
|
||||||
if (videoSizeEstimations != null) {
|
LaunchedEffect(videoSizeEstimations) {
|
||||||
// Check if the video size estimations are too large for the max upload size
|
if (videoSizeEstimations != null) {
|
||||||
displayFileTooLargeError = videoSizeEstimations.none { it.canUpload }
|
// Check if the video size estimations are too large for the max upload size
|
||||||
|
displayFileTooLargeError = videoSizeEstimations.none { it.canUpload }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,32 +229,19 @@ class AttachmentsPreviewPresenter(
|
|||||||
when (event) {
|
when (event) {
|
||||||
is AttachmentsPreviewEvent.SendAttachment -> {
|
is AttachmentsPreviewEvent.SendAttachment -> {
|
||||||
ongoingSendAttachmentJob.value = coroutineScope.launch {
|
ongoingSendAttachmentJob.value = coroutineScope.launch {
|
||||||
// If the media optimization selector is displayed, we need to wait for the user to select the options
|
if (preprocessMediaJob?.isActive != true && sendActionState.value !is SendActionState.Sending.ReadyToUpload) {
|
||||||
// before we can pre-process the media.
|
val configs = mediaOptimizationSelectorStates.map {
|
||||||
if (mediaOptimizationSelectorState.displayMediaSelectorViews == true) {
|
MediaOptimizationConfig(
|
||||||
val config = MediaOptimizationConfig(
|
compressImages = it.isImageOptimizationEnabled
|
||||||
compressImages = mediaOptimizationSelectorState.isImageOptimizationEnabled == true,
|
?: mediaOptimizationConfigProvider.get().compressImages,
|
||||||
videoCompressionPreset = mediaOptimizationSelectorState.selectedVideoPreset ?: VideoCompressionPreset.STANDARD,
|
videoCompressionPreset = it.selectedVideoPreset
|
||||||
)
|
?: mediaOptimizationConfigProvider.get().videoCompressionPreset,
|
||||||
preprocessMediaJob = coroutineScope.launch(dispatchers.io) {
|
|
||||||
preProcessAttachments(
|
|
||||||
attachments = editedAttachments,
|
|
||||||
mediaOptimizationConfig = config,
|
|
||||||
displayProgress = true,
|
|
||||||
sendActionState = sendActionState,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (preprocessMediaJob?.isActive != true && sendActionState.value !is SendActionState.Sending.ReadyToUpload) {
|
|
||||||
val config = MediaOptimizationConfig(
|
|
||||||
compressImages = mediaOptimizationSelectorState.isImageOptimizationEnabled
|
|
||||||
?: mediaOptimizationConfigProvider.get().compressImages,
|
|
||||||
videoCompressionPreset = mediaOptimizationSelectorState.selectedVideoPreset
|
|
||||||
?: mediaOptimizationConfigProvider.get().videoCompressionPreset,
|
|
||||||
)
|
|
||||||
preprocessMediaJob = coroutineScope.launch(dispatchers.io) {
|
preprocessMediaJob = coroutineScope.launch(dispatchers.io) {
|
||||||
preProcessAttachments(
|
preProcessAttachments(
|
||||||
attachments = editedAttachments,
|
attachments = editedAttachments,
|
||||||
mediaOptimizationConfig = config,
|
mediaOptimizationConfigs = configs,
|
||||||
displayProgress = true,
|
displayProgress = true,
|
||||||
sendActionState = sendActionState,
|
sendActionState = sendActionState,
|
||||||
)
|
)
|
||||||
@@ -350,7 +362,7 @@ class AttachmentsPreviewPresenter(
|
|||||||
if (!pendingState.edits.hasChanges) {
|
if (!pendingState.edits.hasChanges) {
|
||||||
editedTempFiles[currentIndex]?.safeDelete()
|
editedTempFiles[currentIndex]?.safeDelete()
|
||||||
editedTempFiles = editedTempFiles - currentIndex
|
editedTempFiles = editedTempFiles - currentIndex
|
||||||
currentAttachment = attachmentsAndEdits[currentIndex].attachment
|
val currentAttachment = attachmentsAndEdits[currentIndex].attachment
|
||||||
attachmentsAndEdits = attachmentsAndEdits.toMutableList().also {
|
attachmentsAndEdits = attachmentsAndEdits.toMutableList().also {
|
||||||
it[currentIndex] = AttachmentAndEdits(
|
it[currentIndex] = AttachmentAndEdits(
|
||||||
currentAttachment,
|
currentAttachment,
|
||||||
@@ -374,7 +386,7 @@ class AttachmentsPreviewPresenter(
|
|||||||
onSuccess = { editedMedia ->
|
onSuccess = { editedMedia ->
|
||||||
editedTempFiles[currentIndex]?.safeDelete()
|
editedTempFiles[currentIndex]?.safeDelete()
|
||||||
editedTempFiles = editedTempFiles + (currentIndex to editedMedia.file)
|
editedTempFiles = editedTempFiles + (currentIndex to editedMedia.file)
|
||||||
currentAttachment = Attachment.Media(editedMedia.localMedia)
|
val currentAttachment = Attachment.Media(editedMedia.localMedia)
|
||||||
attachmentsAndEdits = attachmentsAndEdits.toMutableList().also {
|
attachmentsAndEdits = attachmentsAndEdits.toMutableList().also {
|
||||||
it[currentIndex] = AttachmentAndEdits(
|
it[currentIndex] = AttachmentAndEdits(
|
||||||
currentAttachment,
|
currentAttachment,
|
||||||
@@ -409,28 +421,50 @@ class AttachmentsPreviewPresenter(
|
|||||||
displayImageEditError = displayImageEditError,
|
displayImageEditError = displayImageEditError,
|
||||||
sendActionState = sendActionState.value,
|
sendActionState = sendActionState.value,
|
||||||
textEditorState = textEditorState,
|
textEditorState = textEditorState,
|
||||||
mediaOptimizationSelectorState = mediaOptimizationSelectorState,
|
mediaOptimizationSelectorState = mediaOptimizationSelectorStates[currentIndex],
|
||||||
displayFileTooLargeError = displayFileTooLargeError,
|
displayFileTooLargeError = displayFileTooLargeError,
|
||||||
currentIndex = currentIndex,
|
currentIndex = currentIndex,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun getAutoPreprocessMediaOptimizationConfig(
|
||||||
|
mediaAttachment: Attachment.Media,
|
||||||
|
mediaOptimizationSelectorState: MediaOptimizationSelectorState,
|
||||||
|
): MediaOptimizationConfig {
|
||||||
|
return if (mediaAttachment.sendAsFile) {
|
||||||
|
// If we're sending the media as a file, we can skip image compression and we should select the highest video compression preset that still fits
|
||||||
|
// the upload limit (if the estimations are available)
|
||||||
|
val videoCompressionPreset = videoCompressionPresetSelector.selectBestVideoPreset(
|
||||||
|
expectedVideoPreset = VideoCompressionPreset.HIGH,
|
||||||
|
videoSizeEstimations = mediaOptimizationSelectorState.videoSizeEstimations,
|
||||||
|
).dataOrNull() ?: VideoCompressionPreset.HIGH
|
||||||
|
|
||||||
|
MediaOptimizationConfig(
|
||||||
|
compressImages = false,
|
||||||
|
videoCompressionPreset = videoCompressionPreset,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Otherwise, we just rely on the user preferences for media optimization
|
||||||
|
mediaOptimizationConfigProvider.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun preProcessAttachments(
|
private suspend fun preProcessAttachments(
|
||||||
attachments: List<Attachment>,
|
attachments: List<Attachment>,
|
||||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
mediaOptimizationConfigs: List<MediaOptimizationConfig>,
|
||||||
displayProgress: Boolean,
|
displayProgress: Boolean,
|
||||||
sendActionState: MutableState<SendActionState>,
|
sendActionState: MutableState<SendActionState>,
|
||||||
) {
|
) {
|
||||||
sendActionState.value = SendActionState.Sending.Processing(displayProgress = displayProgress)
|
sendActionState.value = SendActionState.Sending.Processing(displayProgress = displayProgress)
|
||||||
val mediaUploadInfos = mutableListOf<MediaUploadInfo>()
|
val mediaUploadInfos = mutableListOf<MediaUploadInfo>()
|
||||||
for (attachment in attachments) {
|
attachments.forEachIndexed { index, attachment ->
|
||||||
when (attachment) {
|
when (attachment) {
|
||||||
is Attachment.Media -> {
|
is Attachment.Media -> {
|
||||||
mediaSender.preProcessMedia(
|
mediaSender.preProcessMedia(
|
||||||
uri = attachment.localMedia.uri,
|
uri = attachment.localMedia.uri,
|
||||||
mimeType = attachment.localMedia.info.mimeType,
|
mimeType = attachment.localMedia.info.mimeType,
|
||||||
mediaOptimizationConfig = mediaOptimizationConfig,
|
mediaOptimizationConfig = mediaOptimizationConfigs[index],
|
||||||
).fold(
|
).fold(
|
||||||
onSuccess = { mediaUploadInfo ->
|
onSuccess = { mediaUploadInfo ->
|
||||||
Timber.d("Media ${mediaUploadInfo.file.path.orEmpty().hash()} finished processing")
|
Timber.d("Media ${mediaUploadInfo.file.path.orEmpty().hash()} finished processing")
|
||||||
|
|||||||
+1
@@ -146,6 +146,7 @@ fun aMediaOptimisationSelectorState(
|
|||||||
displayMediaSelectorViews: Boolean = true,
|
displayMediaSelectorViews: Boolean = true,
|
||||||
displayVideoPresetSelectorDialog: Boolean = false,
|
displayVideoPresetSelectorDialog: Boolean = false,
|
||||||
) = MediaOptimizationSelectorState(
|
) = MediaOptimizationSelectorState(
|
||||||
|
index = 0,
|
||||||
maxUploadSize = AsyncData.Success(maxUploadSize),
|
maxUploadSize = AsyncData.Success(maxUploadSize),
|
||||||
videoSizeEstimations = videoSizeEstimations,
|
videoSizeEstimations = videoSizeEstimations,
|
||||||
isImageOptimizationEnabled = isImageOptimizationEnabled,
|
isImageOptimizationEnabled = isImageOptimizationEnabled,
|
||||||
|
|||||||
+1
-1
@@ -346,7 +346,7 @@ private fun AttachmentPreviewContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val mediaInfo = (state.attachments.first() as? Attachment.Media)?.localMedia?.info
|
val mediaInfo = (state.attachments[state.currentIndex] as? Attachment.Media)?.localMedia?.info
|
||||||
if (mediaInfo?.isImageAttachment() == true) {
|
if (mediaInfo?.isImageAttachment() == true) {
|
||||||
ImageOptimizationSelector(state.mediaOptimizationSelectorState)
|
ImageOptimizationSelector(state.mediaOptimizationSelectorState)
|
||||||
} else if (mediaInfo?.mimeType?.isMimeTypeVideo() == true) {
|
} else if (mediaInfo?.mimeType?.isMimeTypeVideo() == true) {
|
||||||
|
|||||||
+3
@@ -36,6 +36,7 @@ import kotlin.math.roundToLong
|
|||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class DefaultMediaOptimizationSelectorPresenter(
|
class DefaultMediaOptimizationSelectorPresenter(
|
||||||
|
@Assisted private val index: Int,
|
||||||
@Assisted private val localMedia: LocalMedia,
|
@Assisted private val localMedia: LocalMedia,
|
||||||
@Assisted private val sendAsFile: Boolean,
|
@Assisted private val sendAsFile: Boolean,
|
||||||
private val maxUploadSizeProvider: MaxUploadSizeProvider,
|
private val maxUploadSizeProvider: MaxUploadSizeProvider,
|
||||||
@@ -48,6 +49,7 @@ class DefaultMediaOptimizationSelectorPresenter(
|
|||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory : MediaOptimizationSelectorPresenter.Factory {
|
interface Factory : MediaOptimizationSelectorPresenter.Factory {
|
||||||
override fun create(
|
override fun create(
|
||||||
|
index: Int,
|
||||||
localMedia: LocalMedia,
|
localMedia: LocalMedia,
|
||||||
sendAsFile: Boolean,
|
sendAsFile: Boolean,
|
||||||
): DefaultMediaOptimizationSelectorPresenter
|
): DefaultMediaOptimizationSelectorPresenter
|
||||||
@@ -183,6 +185,7 @@ class DefaultMediaOptimizationSelectorPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return MediaOptimizationSelectorState(
|
return MediaOptimizationSelectorState(
|
||||||
|
index = index,
|
||||||
maxUploadSize = maxUploadSize,
|
maxUploadSize = maxUploadSize,
|
||||||
videoSizeEstimations = videoSizeEstimations,
|
videoSizeEstimations = videoSizeEstimations,
|
||||||
isImageOptimizationEnabled = selectedImageOptimization.dataOrNull(),
|
isImageOptimizationEnabled = selectedImageOptimization.dataOrNull(),
|
||||||
|
|||||||
+1
@@ -14,6 +14,7 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
|||||||
fun interface MediaOptimizationSelectorPresenter : Presenter<MediaOptimizationSelectorState> {
|
fun interface MediaOptimizationSelectorPresenter : Presenter<MediaOptimizationSelectorState> {
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(
|
fun create(
|
||||||
|
index: Int,
|
||||||
localMedia: LocalMedia,
|
localMedia: LocalMedia,
|
||||||
sendAsFile: Boolean,
|
sendAsFile: Boolean,
|
||||||
): MediaOptimizationSelectorPresenter
|
): MediaOptimizationSelectorPresenter
|
||||||
|
|||||||
+2
@@ -13,6 +13,8 @@ import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
|||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
|
||||||
data class MediaOptimizationSelectorState(
|
data class MediaOptimizationSelectorState(
|
||||||
|
// Adding an index helps to fix a Compose issue where the state of the wrong item is updated
|
||||||
|
val index: Int,
|
||||||
val maxUploadSize: AsyncData<Long>,
|
val maxUploadSize: AsyncData<Long>,
|
||||||
val videoSizeEstimations: AsyncData<ImmutableList<VideoUploadEstimation>>,
|
val videoSizeEstimations: AsyncData<ImmutableList<VideoUploadEstimation>>,
|
||||||
val isImageOptimizationEnabled: Boolean?,
|
val isImageOptimizationEnabled: Boolean?,
|
||||||
|
|||||||
+21
@@ -23,6 +23,7 @@ import io.element.android.features.messages.impl.attachments.preview.imageeditor
|
|||||||
import io.element.android.features.messages.impl.attachments.preview.imageeditor.NormalizedCropRect
|
import io.element.android.features.messages.impl.attachments.preview.imageeditor.NormalizedCropRect
|
||||||
import io.element.android.features.messages.impl.attachments.preview.imageeditor.assertIsSimilarTo
|
import io.element.android.features.messages.impl.attachments.preview.imageeditor.assertIsSimilarTo
|
||||||
import io.element.android.features.messages.impl.attachments.video.MediaOptimizationSelectorState
|
import io.element.android.features.messages.impl.attachments.video.MediaOptimizationSelectorState
|
||||||
|
import io.element.android.features.messages.impl.attachments.video.VideoCompressionPresetSelector
|
||||||
import io.element.android.features.messages.impl.attachments.video.VideoUploadEstimation
|
import io.element.android.features.messages.impl.attachments.video.VideoUploadEstimation
|
||||||
import io.element.android.features.messages.impl.fixtures.aMediaAttachment
|
import io.element.android.features.messages.impl.fixtures.aMediaAttachment
|
||||||
import io.element.android.features.messages.test.attachments.video.FakeMediaOptimizationSelectorPresenterFactory
|
import io.element.android.features.messages.test.attachments.video.FakeMediaOptimizationSelectorPresenterFactory
|
||||||
@@ -93,6 +94,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
createAttachmentsPreviewPresenter().test {
|
createAttachmentsPreviewPresenter().test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
}
|
}
|
||||||
@@ -117,6 +119,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
|
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
|
||||||
@@ -151,6 +154,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
// Pre-processing finishes
|
// Pre-processing finishes
|
||||||
@@ -187,6 +191,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
|
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
|
||||||
@@ -216,6 +221,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
|
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
|
||||||
@@ -239,6 +245,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
// Pre-processing finishes
|
// Pre-processing finishes
|
||||||
@@ -261,6 +268,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
initialState.eventSink(AttachmentsPreviewEvent.CancelAndDismiss)
|
initialState.eventSink(AttachmentsPreviewEvent.CancelAndDismiss)
|
||||||
@@ -292,6 +300,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
initialState.textEditorState.setMarkdown(A_CAPTION)
|
initialState.textEditorState.setMarkdown(A_CAPTION)
|
||||||
@@ -333,6 +342,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
initialState.textEditorState.setMarkdown(A_CAPTION)
|
initialState.textEditorState.setMarkdown(A_CAPTION)
|
||||||
@@ -374,6 +384,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
initialState.textEditorState.setMarkdown(A_CAPTION)
|
initialState.textEditorState.setMarkdown(A_CAPTION)
|
||||||
@@ -408,6 +419,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
)
|
)
|
||||||
val presenter = createAttachmentsPreviewPresenter(room = room, onDoneListener = onDoneListenerResult)
|
val presenter = createAttachmentsPreviewPresenter(room = room, onDoneListener = onDoneListenerResult)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
|
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
|
||||||
@@ -437,6 +449,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = onDoneListenerResult,
|
onDoneListener = onDoneListenerResult,
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||||
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
|
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
|
||||||
@@ -469,6 +482,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = onDoneListenerResult,
|
onDoneListener = onDoneListenerResult,
|
||||||
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
|
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
|
||||||
MediaOptimizationSelectorState(
|
MediaOptimizationSelectorState(
|
||||||
|
index = 0,
|
||||||
// Set a max upload size smaller than the file size
|
// Set a max upload size smaller than the file size
|
||||||
maxUploadSize = AsyncData.Success(maxUploadSize),
|
maxUploadSize = AsyncData.Success(maxUploadSize),
|
||||||
videoSizeEstimations = AsyncData.Uninitialized,
|
videoSizeEstimations = AsyncData.Uninitialized,
|
||||||
@@ -508,6 +522,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
onDoneListener = onDoneListenerResult,
|
onDoneListener = onDoneListenerResult,
|
||||||
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
|
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
|
||||||
MediaOptimizationSelectorState(
|
MediaOptimizationSelectorState(
|
||||||
|
index = 0,
|
||||||
// Set a max upload size smaller than the file size
|
// Set a max upload size smaller than the file size
|
||||||
maxUploadSize = AsyncData.Success(Long.MAX_VALUE),
|
maxUploadSize = AsyncData.Success(Long.MAX_VALUE),
|
||||||
videoSizeEstimations = AsyncData.Success(
|
videoSizeEstimations = AsyncData.Success(
|
||||||
@@ -565,6 +580,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(AttachmentsPreviewEvent.OpenImageEditor)
|
initialState.eventSink(AttachmentsPreviewEvent.OpenImageEditor)
|
||||||
val editorState = awaitItem()
|
val editorState = awaitItem()
|
||||||
@@ -775,6 +791,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
attachments = listOf(Attachment.Media(localMedia)),
|
attachments = listOf(Attachment.Media(localMedia)),
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.canEditImage).isTrue()
|
assertThat(initialState.canEditImage).isTrue()
|
||||||
|
|
||||||
@@ -833,6 +850,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
displayMediaQualitySelectorViews = false,
|
displayMediaQualitySelectorViews = false,
|
||||||
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
|
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
|
||||||
MediaOptimizationSelectorState(
|
MediaOptimizationSelectorState(
|
||||||
|
index = 0,
|
||||||
maxUploadSize = AsyncData.Success(250_000_000L),
|
maxUploadSize = AsyncData.Success(250_000_000L),
|
||||||
videoSizeEstimations = AsyncData.Success(
|
videoSizeEstimations = AsyncData.Success(
|
||||||
persistentListOf(
|
persistentListOf(
|
||||||
@@ -933,6 +951,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
mediaOptimizationSelectorPresenterFactory: FakeMediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory(
|
mediaOptimizationSelectorPresenterFactory: FakeMediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory(
|
||||||
fakePresenter = {
|
fakePresenter = {
|
||||||
MediaOptimizationSelectorState(
|
MediaOptimizationSelectorState(
|
||||||
|
index = 0,
|
||||||
maxUploadSize = AsyncData.Uninitialized,
|
maxUploadSize = AsyncData.Uninitialized,
|
||||||
videoSizeEstimations = AsyncData.Uninitialized,
|
videoSizeEstimations = AsyncData.Uninitialized,
|
||||||
isImageOptimizationEnabled = null,
|
isImageOptimizationEnabled = null,
|
||||||
@@ -957,6 +976,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
Result.failure(IllegalStateException("Check test values"))
|
Result.failure(IllegalStateException("Check test values"))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
videoCompressionPresetSelector: VideoCompressionPresetSelector = VideoCompressionPresetSelector(),
|
||||||
): AttachmentsPreviewPresenter {
|
): AttachmentsPreviewPresenter {
|
||||||
return AttachmentsPreviewPresenter(
|
return AttachmentsPreviewPresenter(
|
||||||
attachments = attachments.toImmutableList(),
|
attachments = attachments.toImmutableList(),
|
||||||
@@ -977,6 +997,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
|
|||||||
sessionCoroutineScope = this,
|
sessionCoroutineScope = this,
|
||||||
dispatchers = testCoroutineDispatchers(),
|
dispatchers = testCoroutineDispatchers(),
|
||||||
mediaOptimizationSelectorPresenterFactory = mediaOptimizationSelectorPresenterFactory,
|
mediaOptimizationSelectorPresenterFactory = mediaOptimizationSelectorPresenterFactory,
|
||||||
|
videoCompressionPresetSelector = videoCompressionPresetSelector,
|
||||||
timelineMode = timelineMode,
|
timelineMode = timelineMode,
|
||||||
inReplyToEventId = null,
|
inReplyToEventId = null,
|
||||||
mediaOptimizationConfigProvider = mediaOptimizationConfigProvider,
|
mediaOptimizationConfigProvider = mediaOptimizationConfigProvider,
|
||||||
|
|||||||
+2
@@ -274,6 +274,7 @@ class DefaultMediaOptimizationSelectorPresenterTest : RobolectricTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun createDefaultMediaOptimizationSelectorPresenter(
|
private fun createDefaultMediaOptimizationSelectorPresenter(
|
||||||
|
index: Int = 0,
|
||||||
localMedia: LocalMedia = aLocalMedia(mockMediaUrl, aVideoMediaInfo()),
|
localMedia: LocalMedia = aLocalMedia(mockMediaUrl, aVideoMediaInfo()),
|
||||||
maxUploadSizeProvider: MaxUploadSizeProvider = MaxUploadSizeProvider { Result.success(1_000L) },
|
maxUploadSizeProvider: MaxUploadSizeProvider = MaxUploadSizeProvider { Result.success(1_000L) },
|
||||||
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to true)),
|
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to true)),
|
||||||
@@ -283,6 +284,7 @@ class DefaultMediaOptimizationSelectorPresenterTest : RobolectricTest() {
|
|||||||
sendAsFile: Boolean = false,
|
sendAsFile: Boolean = false,
|
||||||
): DefaultMediaOptimizationSelectorPresenter {
|
): DefaultMediaOptimizationSelectorPresenter {
|
||||||
return DefaultMediaOptimizationSelectorPresenter(
|
return DefaultMediaOptimizationSelectorPresenter(
|
||||||
|
index = index,
|
||||||
localMedia = localMedia,
|
localMedia = localMedia,
|
||||||
sendAsFile = sendAsFile,
|
sendAsFile = sendAsFile,
|
||||||
maxUploadSizeProvider = maxUploadSizeProvider,
|
maxUploadSizeProvider = maxUploadSizeProvider,
|
||||||
|
|||||||
+2
-1
@@ -16,6 +16,7 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
|||||||
class FakeMediaOptimizationSelectorPresenterFactory(
|
class FakeMediaOptimizationSelectorPresenterFactory(
|
||||||
private val fakePresenter: MediaOptimizationSelectorPresenter = MediaOptimizationSelectorPresenter {
|
private val fakePresenter: MediaOptimizationSelectorPresenter = MediaOptimizationSelectorPresenter {
|
||||||
MediaOptimizationSelectorState(
|
MediaOptimizationSelectorState(
|
||||||
|
index = 0,
|
||||||
maxUploadSize = AsyncData.Uninitialized,
|
maxUploadSize = AsyncData.Uninitialized,
|
||||||
videoSizeEstimations = AsyncData.Uninitialized,
|
videoSizeEstimations = AsyncData.Uninitialized,
|
||||||
isImageOptimizationEnabled = null,
|
isImageOptimizationEnabled = null,
|
||||||
@@ -26,7 +27,7 @@ class FakeMediaOptimizationSelectorPresenterFactory(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
) : MediaOptimizationSelectorPresenter.Factory {
|
) : MediaOptimizationSelectorPresenter.Factory {
|
||||||
override fun create(localMedia: LocalMedia, sendAsFile: Boolean): MediaOptimizationSelectorPresenter {
|
override fun create(index: Int, localMedia: LocalMedia, sendAsFile: Boolean): MediaOptimizationSelectorPresenter {
|
||||||
return fakePresenter
|
return fakePresenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user