[Gallery] Set media optimation user choice per media

This commit is contained in:
Benoit Marty
2026-06-30 16:37:58 +02:00
parent 16eec4d024
commit 6cce74db88
9 changed files with 126 additions and 61 deletions
@@ -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.AttachmentImageEdits
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.safeDelete
import io.element.android.libraries.androidutils.hash.hash
@@ -69,6 +71,7 @@ class AttachmentsPreviewPresenter(
private val temporaryUriDeleter: TemporaryUriDeleter,
private val attachmentImageEditor: AttachmentImageEditor,
private val mediaOptimizationSelectorPresenterFactory: MediaOptimizationSelectorPresenter.Factory,
private val videoCompressionPresetSelector: VideoCompressionPresetSelector,
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers,
private val mediaOptimizationConfigProvider: MediaOptimizationConfigProvider,
@@ -97,9 +100,7 @@ class AttachmentsPreviewPresenter(
val sendActionState = remember {
mutableStateOf<SendActionState>(SendActionState.Idle)
}
val originalLocalMedia = remember { (attachments.first() as Attachment.Media).localMedia }
var currentAttachment by remember { mutableStateOf(attachments.first()) }
var canEditImage by remember { mutableStateOf(originalLocalMedia.info.canEditImage()) }
var canEditImage by remember { mutableStateOf(false) }
var imageEditorState by remember { mutableStateOf<AttachmentImageEditorState?>(null) }
var isApplyingImageEdits by remember { mutableStateOf(false) }
var displayImageEditError by remember { mutableStateOf(false) }
@@ -130,37 +131,57 @@ class AttachmentsPreviewPresenter(
var preprocessMediaJob by remember { mutableStateOf<Job?>(null) }
val firstMediaAttachment = attachments.first() as Attachment.Media
val mediaOptimizationSelectorPresenter = remember {
mediaOptimizationSelectorPresenterFactory.create(
localMedia = firstMediaAttachment.localMedia,
sendAsFile = firstMediaAttachment.sendAsFile,
)
val mediaOptimizationSelectorPresenters = remember {
attachments
.filterIsInstance<Attachment.Media>()
.mapIndexed { index, attachment ->
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 }
var displayFileTooLargeError by remember { mutableStateOf(false) }
LaunchedEffect(mediaOptimizationSelectorState.displayMediaSelectorViews, mediaOptimizationSelectorState.selectedVideoPreset) {
if (mediaOptimizationSelectorState.displayMediaSelectorViews == false &&
imageEditorState == null &&
!isApplyingImageEdits
LaunchedEffect(
mediaOptimizationSelectorStates,
imageEditorState,
isApplyingImageEdits,
editedAttachments,
) {
if (mediaOptimizationSelectorStates.any { it.displayMediaSelectorViews == true } ||
imageEditorState != null ||
isApplyingImageEdits
) {
val config = MediaOptimizationConfig(
compressImages = mediaOptimizationSelectorState.isImageOptimizationEnabled ?: mediaOptimizationConfigProvider.get().compressImages,
videoCompressionPreset = mediaOptimizationSelectorState.selectedVideoPreset ?: mediaOptimizationConfigProvider.get().videoCompressionPreset,
// If any of the media optimization selectors are displayed, we don't want to pre-process the media yet
return@LaunchedEffect
}
// 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) {
if (maxUploadSize != null) {
// 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()
LaunchedEffect(videoSizeEstimations) {
if (videoSizeEstimations != null) {
// Check if the video size estimations are too large for the max upload size
displayFileTooLargeError = videoSizeEstimations.none { it.canUpload }
mediaOptimizationSelectorStates.forEach { mediaOptimizationSelectorState ->
val videoSizeEstimations = mediaOptimizationSelectorState.videoSizeEstimations.dataOrNull()
LaunchedEffect(videoSizeEstimations) {
if (videoSizeEstimations != null) {
// 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) {
is AttachmentsPreviewEvent.SendAttachment -> {
ongoingSendAttachmentJob.value = coroutineScope.launch {
// If the media optimization selector is displayed, we need to wait for the user to select the options
// before we can pre-process the media.
if (mediaOptimizationSelectorState.displayMediaSelectorViews == true) {
val config = MediaOptimizationConfig(
compressImages = mediaOptimizationSelectorState.isImageOptimizationEnabled == true,
videoCompressionPreset = mediaOptimizationSelectorState.selectedVideoPreset ?: VideoCompressionPreset.STANDARD,
)
preprocessMediaJob = coroutineScope.launch(dispatchers.io) {
preProcessAttachments(
attachments = editedAttachments,
mediaOptimizationConfig = config,
displayProgress = true,
sendActionState = sendActionState,
if (preprocessMediaJob?.isActive != true && sendActionState.value !is SendActionState.Sending.ReadyToUpload) {
val configs = mediaOptimizationSelectorStates.map {
MediaOptimizationConfig(
compressImages = it.isImageOptimizationEnabled
?: mediaOptimizationConfigProvider.get().compressImages,
videoCompressionPreset = it.selectedVideoPreset
?: mediaOptimizationConfigProvider.get().videoCompressionPreset,
)
}
} 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) {
preProcessAttachments(
attachments = editedAttachments,
mediaOptimizationConfig = config,
mediaOptimizationConfigs = configs,
displayProgress = true,
sendActionState = sendActionState,
)
@@ -350,7 +362,7 @@ class AttachmentsPreviewPresenter(
if (!pendingState.edits.hasChanges) {
editedTempFiles[currentIndex]?.safeDelete()
editedTempFiles = editedTempFiles - currentIndex
currentAttachment = attachmentsAndEdits[currentIndex].attachment
val currentAttachment = attachmentsAndEdits[currentIndex].attachment
attachmentsAndEdits = attachmentsAndEdits.toMutableList().also {
it[currentIndex] = AttachmentAndEdits(
currentAttachment,
@@ -374,7 +386,7 @@ class AttachmentsPreviewPresenter(
onSuccess = { editedMedia ->
editedTempFiles[currentIndex]?.safeDelete()
editedTempFiles = editedTempFiles + (currentIndex to editedMedia.file)
currentAttachment = Attachment.Media(editedMedia.localMedia)
val currentAttachment = Attachment.Media(editedMedia.localMedia)
attachmentsAndEdits = attachmentsAndEdits.toMutableList().also {
it[currentIndex] = AttachmentAndEdits(
currentAttachment,
@@ -409,28 +421,50 @@ class AttachmentsPreviewPresenter(
displayImageEditError = displayImageEditError,
sendActionState = sendActionState.value,
textEditorState = textEditorState,
mediaOptimizationSelectorState = mediaOptimizationSelectorState,
mediaOptimizationSelectorState = mediaOptimizationSelectorStates[currentIndex],
displayFileTooLargeError = displayFileTooLargeError,
currentIndex = currentIndex,
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(
attachments: List<Attachment>,
mediaOptimizationConfig: MediaOptimizationConfig,
mediaOptimizationConfigs: List<MediaOptimizationConfig>,
displayProgress: Boolean,
sendActionState: MutableState<SendActionState>,
) {
sendActionState.value = SendActionState.Sending.Processing(displayProgress = displayProgress)
val mediaUploadInfos = mutableListOf<MediaUploadInfo>()
for (attachment in attachments) {
attachments.forEachIndexed { index, attachment ->
when (attachment) {
is Attachment.Media -> {
mediaSender.preProcessMedia(
uri = attachment.localMedia.uri,
mimeType = attachment.localMedia.info.mimeType,
mediaOptimizationConfig = mediaOptimizationConfig,
mediaOptimizationConfig = mediaOptimizationConfigs[index],
).fold(
onSuccess = { mediaUploadInfo ->
Timber.d("Media ${mediaUploadInfo.file.path.orEmpty().hash()} finished processing")
@@ -146,6 +146,7 @@ fun aMediaOptimisationSelectorState(
displayMediaSelectorViews: Boolean = true,
displayVideoPresetSelectorDialog: Boolean = false,
) = MediaOptimizationSelectorState(
index = 0,
maxUploadSize = AsyncData.Success(maxUploadSize),
videoSizeEstimations = videoSizeEstimations,
isImageOptimizationEnabled = isImageOptimizationEnabled,
@@ -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) {
ImageOptimizationSelector(state.mediaOptimizationSelectorState)
} else if (mediaInfo?.mimeType?.isMimeTypeVideo() == true) {
@@ -36,6 +36,7 @@ import kotlin.math.roundToLong
@AssistedInject
class DefaultMediaOptimizationSelectorPresenter(
@Assisted private val index: Int,
@Assisted private val localMedia: LocalMedia,
@Assisted private val sendAsFile: Boolean,
private val maxUploadSizeProvider: MaxUploadSizeProvider,
@@ -48,6 +49,7 @@ class DefaultMediaOptimizationSelectorPresenter(
@AssistedFactory
interface Factory : MediaOptimizationSelectorPresenter.Factory {
override fun create(
index: Int,
localMedia: LocalMedia,
sendAsFile: Boolean,
): DefaultMediaOptimizationSelectorPresenter
@@ -183,6 +185,7 @@ class DefaultMediaOptimizationSelectorPresenter(
}
return MediaOptimizationSelectorState(
index = index,
maxUploadSize = maxUploadSize,
videoSizeEstimations = videoSizeEstimations,
isImageOptimizationEnabled = selectedImageOptimization.dataOrNull(),
@@ -14,6 +14,7 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia
fun interface MediaOptimizationSelectorPresenter : Presenter<MediaOptimizationSelectorState> {
interface Factory {
fun create(
index: Int,
localMedia: LocalMedia,
sendAsFile: Boolean,
): MediaOptimizationSelectorPresenter
@@ -13,6 +13,8 @@ import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
import kotlinx.collections.immutable.ImmutableList
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 videoSizeEstimations: AsyncData<ImmutableList<VideoUploadEstimation>>,
val isImageOptimizationEnabled: Boolean?,
@@ -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.assertIsSimilarTo
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.fixtures.aMediaAttachment
import io.element.android.features.messages.test.attachments.video.FakeMediaOptimizationSelectorPresenterFactory
@@ -93,6 +94,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
@Test
fun `present - initial state`() = runTest {
createAttachmentsPreviewPresenter().test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
}
@@ -117,6 +119,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
@@ -151,6 +154,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
// Pre-processing finishes
@@ -187,6 +191,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
@@ -216,6 +221,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
@@ -239,6 +245,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
// Pre-processing finishes
@@ -261,6 +268,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvent.CancelAndDismiss)
@@ -292,6 +300,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION)
@@ -333,6 +342,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION)
@@ -374,6 +384,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = { onDoneListener() },
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION)
@@ -408,6 +419,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
)
val presenter = createAttachmentsPreviewPresenter(room = room, onDoneListener = onDoneListenerResult)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
@@ -437,6 +449,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = onDoneListenerResult,
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
@@ -469,6 +482,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = onDoneListenerResult,
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
MediaOptimizationSelectorState(
index = 0,
// Set a max upload size smaller than the file size
maxUploadSize = AsyncData.Success(maxUploadSize),
videoSizeEstimations = AsyncData.Uninitialized,
@@ -508,6 +522,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
onDoneListener = onDoneListenerResult,
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
MediaOptimizationSelectorState(
index = 0,
// Set a max upload size smaller than the file size
maxUploadSize = AsyncData.Success(Long.MAX_VALUE),
videoSizeEstimations = AsyncData.Success(
@@ -565,6 +580,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
initialState.eventSink(AttachmentsPreviewEvent.OpenImageEditor)
val editorState = awaitItem()
@@ -775,6 +791,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
attachments = listOf(Attachment.Media(localMedia)),
)
presenter.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.canEditImage).isTrue()
@@ -833,6 +850,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
displayMediaQualitySelectorViews = false,
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
MediaOptimizationSelectorState(
index = 0,
maxUploadSize = AsyncData.Success(250_000_000L),
videoSizeEstimations = AsyncData.Success(
persistentListOf(
@@ -933,6 +951,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
mediaOptimizationSelectorPresenterFactory: FakeMediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory(
fakePresenter = {
MediaOptimizationSelectorState(
index = 0,
maxUploadSize = AsyncData.Uninitialized,
videoSizeEstimations = AsyncData.Uninitialized,
isImageOptimizationEnabled = null,
@@ -957,6 +976,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
Result.failure(IllegalStateException("Check test values"))
}
},
videoCompressionPresetSelector: VideoCompressionPresetSelector = VideoCompressionPresetSelector(),
): AttachmentsPreviewPresenter {
return AttachmentsPreviewPresenter(
attachments = attachments.toImmutableList(),
@@ -977,6 +997,7 @@ class AttachmentsPreviewPresenterTest : RobolectricTest() {
sessionCoroutineScope = this,
dispatchers = testCoroutineDispatchers(),
mediaOptimizationSelectorPresenterFactory = mediaOptimizationSelectorPresenterFactory,
videoCompressionPresetSelector = videoCompressionPresetSelector,
timelineMode = timelineMode,
inReplyToEventId = null,
mediaOptimizationConfigProvider = mediaOptimizationConfigProvider,
@@ -274,6 +274,7 @@ class DefaultMediaOptimizationSelectorPresenterTest : RobolectricTest() {
}
private fun createDefaultMediaOptimizationSelectorPresenter(
index: Int = 0,
localMedia: LocalMedia = aLocalMedia(mockMediaUrl, aVideoMediaInfo()),
maxUploadSizeProvider: MaxUploadSizeProvider = MaxUploadSizeProvider { Result.success(1_000L) },
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to true)),
@@ -283,6 +284,7 @@ class DefaultMediaOptimizationSelectorPresenterTest : RobolectricTest() {
sendAsFile: Boolean = false,
): DefaultMediaOptimizationSelectorPresenter {
return DefaultMediaOptimizationSelectorPresenter(
index = index,
localMedia = localMedia,
sendAsFile = sendAsFile,
maxUploadSizeProvider = maxUploadSizeProvider,
@@ -16,6 +16,7 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia
class FakeMediaOptimizationSelectorPresenterFactory(
private val fakePresenter: MediaOptimizationSelectorPresenter = MediaOptimizationSelectorPresenter {
MediaOptimizationSelectorState(
index = 0,
maxUploadSize = AsyncData.Uninitialized,
videoSizeEstimations = AsyncData.Uninitialized,
isImageOptimizationEnabled = null,
@@ -26,7 +27,7 @@ class FakeMediaOptimizationSelectorPresenterFactory(
)
}
) : MediaOptimizationSelectorPresenter.Factory {
override fun create(localMedia: LocalMedia, sendAsFile: Boolean): MediaOptimizationSelectorPresenter {
override fun create(index: Int, localMedia: LocalMedia, sendAsFile: Boolean): MediaOptimizationSelectorPresenter {
return fakePresenter
}
}