diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt index 6b5bdb4eaa..355e9e345e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt @@ -15,5 +15,5 @@ import kotlinx.parcelize.Parcelize @Immutable sealed interface Attachment : Parcelable { @Parcelize - data class Media(val localMedia: LocalMedia, val compressIfPossible: Boolean) : Attachment + data class Media(val localMedia: LocalMedia) : Attachment } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index bc410da8e8..42468d66cf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -96,7 +96,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( mediaSender.sendMedia( uri = mediaAttachment.localMedia.uri, mimeType = mediaAttachment.localMedia.info.mimeType, - compressIfPossible = mediaAttachment.compressIfPossible, progressCallback = progressCallback ).getOrThrow() }.fold( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt index 18d9629e6d..718f9cfbe4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt @@ -31,7 +31,6 @@ fun anAttachmentsPreviewState( ) = AttachmentsPreviewState( attachment = Attachment.Media( localMedia = LocalMedia("file://path".toUri(), mediaInfo), - compressIfPossible = true ), sendActionState = sendActionState, eventSink = {} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index 648be160d1..cce78d601e 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -169,7 +169,7 @@ class MessageComposerPresenter @Inject constructor( handlePickedMedia(attachmentsState, uri, mimeType) } val filesPicker = mediaPickerProvider.registerFilePicker(AnyMimeTypes) { uri -> - handlePickedMedia(attachmentsState, uri, compressIfPossible = false) + handlePickedMedia(attachmentsState, uri) } val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker { uri -> handlePickedMedia(attachmentsState, uri, MimeTypes.IMAGE_JPEG) @@ -294,7 +294,6 @@ class MessageComposerPresenter @Inject constructor( name = null, formattedFileSize = null ), - compressIfPossible = true ), attachmentState = attachmentsState, ) @@ -493,7 +492,6 @@ class MessageComposerPresenter @Inject constructor( attachmentsState: MutableState, uri: Uri?, mimeType: String? = null, - compressIfPossible: Boolean = true, ) { if (uri == null) { attachmentsState.value = AttachmentsState.None @@ -505,7 +503,7 @@ class MessageComposerPresenter @Inject constructor( name = null, formattedFileSize = null ) - val mediaAttachment = Attachment.Media(localMedia, compressIfPossible) + val mediaAttachment = Attachment.Media(localMedia) val isPreviewable = when { MimeTypes.isImage(localMedia.info.mimeType) -> true MimeTypes.isVideo(localMedia.info.mimeType) -> true @@ -535,7 +533,6 @@ class MessageComposerPresenter @Inject constructor( mediaSender.sendMedia( uri = uri, mimeType = mimeType, - compressIfPossible = false, progressCallback = progressCallback ).getOrThrow() } diff --git a/features/preferences/impl/build.gradle.kts b/features/preferences/impl/build.gradle.kts index 5dff5a65d4..564db76ee3 100644 --- a/features/preferences/impl/build.gradle.kts +++ b/features/preferences/impl/build.gradle.kts @@ -55,6 +55,7 @@ dependencies { implementation(projects.features.deactivation.api) implementation(projects.features.roomlist.api) implementation(projects.services.analytics.api) + implementation(projects.services.analytics.compose) implementation(projects.services.toolbox.api) implementation(libs.datetime) implementation(libs.coil.compose) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt index 8ea4958a5b..067e7d490c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt @@ -12,6 +12,7 @@ import io.element.android.compound.theme.Theme sealed interface AdvancedSettingsEvents { data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents + data class SetCompressMedia(val compress: Boolean) : AdvancedSettingsEvents data object ChangeTheme : AdvancedSettingsEvents data object CancelChangeTheme : AdvancedSettingsEvents data class SetTheme(val theme: Theme) : AdvancedSettingsEvents diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index 1f22b463e0..ee54325ce1 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -35,6 +35,9 @@ class AdvancedSettingsPresenter @Inject constructor( val isSharePresenceEnabled by sessionPreferencesStore .isSharePresenceEnabled() .collectAsState(initial = true) + val doesCompressMedia by sessionPreferencesStore + .doesCompressMedia() + .collectAsState(initial = false) val theme by remember { appPreferencesStore.getThemeFlow().mapToTheme() } @@ -49,6 +52,9 @@ class AdvancedSettingsPresenter @Inject constructor( is AdvancedSettingsEvents.SetSharePresenceEnabled -> localCoroutineScope.launch { sessionPreferencesStore.setSharePresence(event.enabled) } + is AdvancedSettingsEvents.SetCompressMedia -> localCoroutineScope.launch { + sessionPreferencesStore.setCompressMedia(event.compress) + } AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch { @@ -61,6 +67,7 @@ class AdvancedSettingsPresenter @Inject constructor( return AdvancedSettingsState( isDeveloperModeEnabled = isDeveloperModeEnabled, isSharePresenceEnabled = isSharePresenceEnabled, + doesCompressMedia = doesCompressMedia, theme = theme, showChangeThemeDialog = showChangeThemeDialog, eventSink = { handleEvents(it) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt index 52b70dd0c5..ce598b3e76 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt @@ -12,6 +12,7 @@ import io.element.android.compound.theme.Theme data class AdvancedSettingsState( val isDeveloperModeEnabled: Boolean, val isSharePresenceEnabled: Boolean, + val doesCompressMedia: Boolean, val theme: Theme, val showChangeThemeDialog: Boolean, val eventSink: (AdvancedSettingsEvents) -> Unit diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt index 3d6a39d852..00e1b72923 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt @@ -16,18 +16,21 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider Unit = {}, ) = AdvancedSettingsState( isDeveloperModeEnabled = isDeveloperModeEnabled, - isSharePresenceEnabled = isSendPublicReadReceiptsEnabled, + isSharePresenceEnabled = isSharePresenceEnabled, + doesCompressMedia = doesCompressMedia, theme = Theme.System, showChangeThemeDialog = showChangeThemeDialog, eventSink = eventSink diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt index cc6108c37c..beaf673afd 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.PreviewParameter +import im.vector.app.features.analytics.plan.Interaction import io.element.android.compound.theme.Theme import io.element.android.compound.theme.themes import io.element.android.features.preferences.impl.R @@ -23,6 +24,8 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.ListItem import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.services.analytics.compose.LocalAnalyticsService +import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -32,6 +35,7 @@ fun AdvancedSettingsView( onBackClick: () -> Unit, modifier: Modifier = Modifier, ) { + val analyticsService = LocalAnalyticsService.current PreferencePage( modifier = modifier, onBackClick = onBackClick, @@ -72,6 +76,28 @@ fun AdvancedSettingsView( ), onClick = { state.eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(!state.isSharePresenceEnabled)) } ) + ListItem( + headlineContent = { + Text(text = stringResource(id = R.string.screen_advanced_settings_media_compression_title)) + }, + supportingContent = { + Text(text = stringResource(id = R.string.screen_advanced_settings_media_compression_description)) + }, + trailingContent = ListItemContent.Switch( + checked = state.doesCompressMedia, + ), + onClick = { + val newValue = !state.doesCompressMedia + analyticsService.captureInteraction( + if (newValue) { + Interaction.Name.MobileSettingsOptimizeMediaUploadsEnabled + } else { + Interaction.Name.MobileSettingsOptimizeMediaUploadsDisabled + } + ) + state.eventSink(AdvancedSettingsEvents.SetCompressMedia(newValue)) + } + ) } if (state.showChangeThemeDialog) { diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt index cd9d8f12e2..1634918296 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt @@ -34,6 +34,7 @@ class AdvancedSettingsPresenterTest { assertThat(initialState.isDeveloperModeEnabled).isFalse() assertThat(initialState.showChangeThemeDialog).isFalse() assertThat(initialState.isSharePresenceEnabled).isTrue() + assertThat(initialState.doesCompressMedia).isFalse() assertThat(initialState.theme).isEqualTo(Theme.System) } } @@ -68,6 +69,21 @@ class AdvancedSettingsPresenterTest { } } + @Test + fun `present - compress media off on`() = runTest { + val presenter = createAdvancedSettingsPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitLastSequentialItem() + assertThat(initialState.doesCompressMedia).isFalse() + initialState.eventSink.invoke(AdvancedSettingsEvents.SetCompressMedia(true)) + assertThat(awaitItem().doesCompressMedia).isTrue() + initialState.eventSink.invoke(AdvancedSettingsEvents.SetCompressMedia(false)) + assertThat(awaitItem().doesCompressMedia).isFalse() + } + } + @Test fun `present - change theme`() = runTest { val presenter = createAdvancedSettingsPresenter() diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt index 43d8ebccef..b43112de4e 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt @@ -8,12 +8,18 @@ package io.element.android.features.preferences.impl.advanced import androidx.activity.ComponentActivity +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.Interaction import io.element.android.compound.theme.Theme import io.element.android.features.preferences.impl.R import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.compose.LocalAnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.clickOn @@ -91,16 +97,64 @@ class AdvancedSettingsViewTest { rule.clickOn(R.string.screen_advanced_settings_share_presence) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true)) } + + @Test + fun `clicking on media to enable compression emits the expected event`() { + val eventsRecorder = EventsRecorder() + val analyticsService = FakeAnalyticsService() + rule.setAdvancedSettingsView( + state = aAdvancedSettingsState( + eventSink = eventsRecorder, + ), + analyticsService = analyticsService + ) + rule.clickOn(R.string.screen_advanced_settings_media_compression_description) + eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(true)) + assertThat(analyticsService.capturedEvents).isEqualTo( + listOf( + Interaction( + name = Interaction.Name.MobileSettingsOptimizeMediaUploadsEnabled + ) + ) + ) + } + + @Test + fun `clicking on media to disable compression emits the expected event`() { + val eventsRecorder = EventsRecorder() + val analyticsService = FakeAnalyticsService() + rule.setAdvancedSettingsView( + state = aAdvancedSettingsState( + doesCompressMedia = true, + eventSink = eventsRecorder, + ), + analyticsService = analyticsService + ) + rule.clickOn(R.string.screen_advanced_settings_media_compression_description) + eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(false)) + assertThat(analyticsService.capturedEvents).isEqualTo( + listOf( + Interaction( + name = Interaction.Name.MobileSettingsOptimizeMediaUploadsDisabled + ) + ) + ) + } } private fun AndroidComposeTestRule.setAdvancedSettingsView( state: AdvancedSettingsState, + analyticsService: AnalyticsService = FakeAnalyticsService(), onBackClick: () -> Unit = EnsureNeverCalled(), ) { setContent { - AdvancedSettingsView( - state = state, - onBackClick = onBackClick, - ) + CompositionLocalProvider( + LocalAnalyticsService provides analyticsService, + ) { + AdvancedSettingsView( + state = state, + onBackClick = onBackClick, + ) + } } } diff --git a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt index fb11994e4d..7f9210b4f7 100644 --- a/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt +++ b/features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaSender +import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -31,6 +32,7 @@ class SharePresenter @AssistedInject constructor( private val shareIntentHandler: ShareIntentHandler, private val matrixClient: MatrixClient, private val mediaPreProcessor: MediaPreProcessor, + private val sessionPreferencesStore: SessionPreferencesStore, ) : Presenter { @AssistedFactory interface Factory { @@ -71,13 +73,16 @@ class SharePresenter @AssistedInject constructor( roomIds .map { roomId -> val room = matrixClient.getRoom(roomId) ?: return@map false - val mediaSender = MediaSender(preProcessor = mediaPreProcessor, room = room) + val mediaSender = MediaSender( + preProcessor = mediaPreProcessor, + room = room, + sessionPreferencesStore = sessionPreferencesStore, + ) filesToShare .map { fileToShare -> mediaSender.sendMedia( uri = fileToShare.uri, mimeType = fileToShare.mimeType, - compressIfPossible = true, ).isSuccess } .all { it } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b460591b5c..05ee33a996 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -193,7 +193,7 @@ zxing_cpp = "io.github.zxing-cpp:android:2.2.0" posthog = "com.posthog:posthog-android:3.8.2" sentry = "io.sentry:sentry-android:7.15.0" # main branch can be tested replacing the version with main-SNAPSHOT -matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.27.0" +matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.28.0" # Emojibase matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.3.3" diff --git a/libraries/mediaupload/api/build.gradle.kts b/libraries/mediaupload/api/build.gradle.kts index 34e409d0aa..33ff16a527 100644 --- a/libraries/mediaupload/api/build.gradle.kts +++ b/libraries/mediaupload/api/build.gradle.kts @@ -23,10 +23,12 @@ dependencies { implementation(projects.libraries.core) implementation(projects.libraries.di) api(projects.libraries.matrix.api) + api(projects.libraries.preferences.api) implementation(libs.inject) implementation(libs.coroutines.core) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.libraries.preferences.test) testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.tests.testutils) testImplementation(libs.test.junit) diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt index c2d34a69b2..a47629d4f2 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt @@ -12,14 +12,17 @@ import io.element.android.libraries.core.extensions.flatMapCatching import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.first import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject class MediaSender @Inject constructor( private val preProcessor: MediaPreProcessor, private val room: MatrixRoom, + private val sessionPreferencesStore: SessionPreferencesStore, ) { private val ongoingUploadJobs = ConcurrentHashMap() val hasOngoingMediaUploads get() = ongoingUploadJobs.isNotEmpty() @@ -27,11 +30,11 @@ class MediaSender @Inject constructor( suspend fun sendMedia( uri: Uri, mimeType: String, - compressIfPossible: Boolean, caption: String? = null, formattedCaption: String? = null, progressCallback: ProgressCallback? = null ): Result { + val compressIfPossible = sessionPreferencesStore.doesCompressMedia().first() return preProcessor .process( uri = uri, @@ -49,6 +52,7 @@ class MediaSender @Inject constructor( } .handleSendResult() } + suspend fun sendVoiceMessage( uri: Uri, mimeType: String, @@ -60,7 +64,7 @@ class MediaSender @Inject constructor( uri = uri, mimeType = mimeType, deleteOriginal = true, - compressIfPossible = false + compressIfPossible = false, ) .flatMapCatching { info -> val audioInfo = (info as MediaUploadInfo.Audio).audioInfo diff --git a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt index 2ae51d1c6b..7724855cc3 100644 --- a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt +++ b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt @@ -15,6 +15,8 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor +import io.element.android.libraries.preferences.api.store.SessionPreferencesStore +import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -33,7 +35,7 @@ class MediaSenderTest { val sender = aMediaSender(preProcessor) val uri = Uri.parse("content://image.jpg") - sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, compressIfPossible = true) + sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) assertThat(preProcessor.processCallCount).isEqualTo(1) } @@ -49,7 +51,7 @@ class MediaSenderTest { val sender = aMediaSender(room = room) val uri = Uri.parse("content://image.jpg") - sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, compressIfPossible = true) + sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) sendMediaResult.assertions().isCalledOnce() } @@ -61,7 +63,7 @@ class MediaSenderTest { val sender = aMediaSender(preProcessor) val uri = Uri.parse("content://image.jpg") - val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, compressIfPossible = true) + val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) assertThat(result.exceptionOrNull()).isNotNull() } @@ -74,7 +76,7 @@ class MediaSenderTest { val sender = aMediaSender(room = room) val uri = Uri.parse("content://image.jpg") - val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, compressIfPossible = true) + val result = sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) assertThat(result.exceptionOrNull()).isNotNull() } @@ -88,7 +90,7 @@ class MediaSenderTest { val sender = aMediaSender(room = room) val sendJob = launch { val uri = Uri.parse("content://image.jpg") - sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, compressIfPossible = true) + sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg) } // Wait until several internal tasks run and the file is being uploaded advanceTimeBy(3L) @@ -109,8 +111,10 @@ class MediaSenderTest { private fun aMediaSender( preProcessor: MediaPreProcessor = FakeMediaPreProcessor(), room: MatrixRoom = FakeMatrixRoom(), + sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(), ) = MediaSender( - preProcessor, - room, + preProcessor = preProcessor, + room = room, + sessionPreferencesStore = sessionPreferencesStore, ) } diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt index 92fc674694..69d2e41baa 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt @@ -36,7 +36,7 @@ class ImageCompressor @Inject constructor( resizeMode: ResizeMode, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, orientation: Int = ExifInterface.ORIENTATION_UNDEFINED, - desiredQuality: Int = 80, + desiredQuality: Int = 78, ): Result = withContext(dispatchers.io) { runCatching { val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow() diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt index 961d705d79..760679b32f 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt @@ -29,7 +29,7 @@ class VideoCompressor @Inject constructor( val future = Transcoder.into(tmpFile.path) .setVideoTrackStrategy( DefaultVideoStrategy.Builder() - .addResizer(AtMostResizer(1920, 1080)) + .addResizer(AtMostResizer(720, 480)) .build() ) .addDataSource(context, uri) diff --git a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt index 44d91d4deb..0cae7c7032 100644 --- a/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt +++ b/libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt @@ -28,5 +28,9 @@ interface SessionPreferencesStore { suspend fun setSkipSessionVerification(skip: Boolean) fun isSessionVerificationSkipped(): Flow + suspend fun setCompressMedia(compress: Boolean) + fun doesCompressMedia(): Flow + + suspend fun clear() } diff --git a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt index b8f58f3501..3fa33eab93 100644 --- a/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt +++ b/libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt @@ -41,6 +41,7 @@ class DefaultSessionPreferencesStore( private val sendTypingNotificationsKey = booleanPreferencesKey("sendTypingNotifications") private val renderTypingNotificationsKey = booleanPreferencesKey("renderTypingNotifications") private val skipSessionVerification = booleanPreferencesKey("skipSessionVerification") + private val compressMedia = booleanPreferencesKey("compressMedia") private val dataStoreFile = storeFile(context, sessionId) private val store = PreferenceDataStoreFactory.create( @@ -81,6 +82,9 @@ class DefaultSessionPreferencesStore( override suspend fun setSkipSessionVerification(skip: Boolean) = update(skipSessionVerification, skip) override fun isSessionVerificationSkipped(): Flow = get(skipSessionVerification) { false } + override suspend fun setCompressMedia(compress: Boolean) = update(compressMedia, compress) + override fun doesCompressMedia(): Flow = get(compressMedia) { false } + override suspend fun clear() { dataStoreFile.safeDelete() } diff --git a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt index 84dc388b75..dde117adc0 100644 --- a/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt +++ b/libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt @@ -18,6 +18,7 @@ class InMemorySessionPreferencesStore( isSendTypingNotificationsEnabled: Boolean = true, isRenderTypingNotificationsEnabled: Boolean = true, isSessionVerificationSkipped: Boolean = false, + doesCompressMedia: Boolean = false, ) : SessionPreferencesStore { private val isSharePresenceEnabled = MutableStateFlow(isSharePresenceEnabled) private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled) @@ -25,6 +26,7 @@ class InMemorySessionPreferencesStore( private val isSendTypingNotificationsEnabled = MutableStateFlow(isSendTypingNotificationsEnabled) private val isRenderTypingNotificationsEnabled = MutableStateFlow(isRenderTypingNotificationsEnabled) private val isSessionVerificationSkipped = MutableStateFlow(isSessionVerificationSkipped) + private val doesCompressMedia = MutableStateFlow(doesCompressMedia) var clearCallCount = 0 private set @@ -66,6 +68,10 @@ class InMemorySessionPreferencesStore( return isSessionVerificationSkipped } + override suspend fun setCompressMedia(compress: Boolean) = doesCompressMedia.emit(compress) + + override fun doesCompressMedia(): Flow = doesCompressMedia + override suspend fun clear() { clearCallCount++ isSendPublicReadReceiptsEnabled.tryEmit(true)