Browse Source

Merge pull request #3744 from element-hq/feature/bma/resizeMedia

Add setting to compress image and video
pull/3702/merge
Benoit Marty 21 hours ago committed by GitHub
parent
commit
870ae5fb54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt
  2. 1
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt
  3. 1
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt
  4. 7
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt
  5. 6
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt
  6. 3
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MediaAttachmentFixtures.kt
  7. 2
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt
  8. 3
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt
  9. 1
      features/preferences/impl/build.gradle.kts
  10. 1
      features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsEvents.kt
  11. 7
      features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt
  12. 1
      features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsState.kt
  13. 9
      features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt
  14. 26
      features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsView.kt
  15. 16
      features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenterTest.kt
  16. 54
      features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsViewTest.kt
  17. 1
      features/share/impl/build.gradle.kts
  18. 9
      features/share/impl/src/main/kotlin/io/element/android/features/share/impl/SharePresenter.kt
  19. 4
      features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt
  20. 2
      gradle/libs.versions.toml
  21. 2
      libraries/mediaupload/api/build.gradle.kts
  22. 8
      libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaSender.kt
  23. 18
      libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt
  24. 2
      libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt
  25. 2
      libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/VideoCompressor.kt
  26. 6
      libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt
  27. 3
      libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt
  28. 4
      libraries/preferences/impl/src/main/kotlin/io/element/android/libraries/preferences/impl/store/DefaultSessionPreferencesStore.kt
  29. 6
      libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt
  30. 4
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png
  31. 4
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png
  32. 4
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en.png
  33. 4
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png
  34. 3
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en.png
  35. 4
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png
  36. 4
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png
  37. 4
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en.png
  38. 4
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png
  39. 3
      tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en.png

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/Attachment.kt

@ -15,5 +15,5 @@ import kotlinx.parcelize.Parcelize
@Immutable @Immutable
sealed interface Attachment : Parcelable { sealed interface Attachment : Parcelable {
@Parcelize @Parcelize
data class Media(val localMedia: LocalMedia, val compressIfPossible: Boolean) : Attachment data class Media(val localMedia: LocalMedia) : Attachment
} }

1
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( mediaSender.sendMedia(
uri = mediaAttachment.localMedia.uri, uri = mediaAttachment.localMedia.uri,
mimeType = mediaAttachment.localMedia.info.mimeType, mimeType = mediaAttachment.localMedia.info.mimeType,
compressIfPossible = mediaAttachment.compressIfPossible,
progressCallback = progressCallback progressCallback = progressCallback
).getOrThrow() ).getOrThrow()
}.fold( }.fold(

1
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewStateProvider.kt

@ -31,7 +31,6 @@ fun anAttachmentsPreviewState(
) = AttachmentsPreviewState( ) = AttachmentsPreviewState(
attachment = Attachment.Media( attachment = Attachment.Media(
localMedia = LocalMedia("file://path".toUri(), mediaInfo), localMedia = LocalMedia("file://path".toUri(), mediaInfo),
compressIfPossible = true
), ),
sendActionState = sendActionState, sendActionState = sendActionState,
eventSink = {} eventSink = {}

7
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) handlePickedMedia(attachmentsState, uri, mimeType)
} }
val filesPicker = mediaPickerProvider.registerFilePicker(AnyMimeTypes) { uri -> val filesPicker = mediaPickerProvider.registerFilePicker(AnyMimeTypes) { uri ->
handlePickedMedia(attachmentsState, uri, compressIfPossible = false) handlePickedMedia(attachmentsState, uri)
} }
val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker { uri -> val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker { uri ->
handlePickedMedia(attachmentsState, uri, MimeTypes.IMAGE_JPEG) handlePickedMedia(attachmentsState, uri, MimeTypes.IMAGE_JPEG)
@ -294,7 +294,6 @@ class MessageComposerPresenter @Inject constructor(
name = null, name = null,
formattedFileSize = null formattedFileSize = null
), ),
compressIfPossible = true
), ),
attachmentState = attachmentsState, attachmentState = attachmentsState,
) )
@ -493,7 +492,6 @@ class MessageComposerPresenter @Inject constructor(
attachmentsState: MutableState<AttachmentsState>, attachmentsState: MutableState<AttachmentsState>,
uri: Uri?, uri: Uri?,
mimeType: String? = null, mimeType: String? = null,
compressIfPossible: Boolean = true,
) { ) {
if (uri == null) { if (uri == null) {
attachmentsState.value = AttachmentsState.None attachmentsState.value = AttachmentsState.None
@ -505,7 +503,7 @@ class MessageComposerPresenter @Inject constructor(
name = null, name = null,
formattedFileSize = null formattedFileSize = null
) )
val mediaAttachment = Attachment.Media(localMedia, compressIfPossible) val mediaAttachment = Attachment.Media(localMedia)
val isPreviewable = when { val isPreviewable = when {
MimeTypes.isImage(localMedia.info.mimeType) -> true MimeTypes.isImage(localMedia.info.mimeType) -> true
MimeTypes.isVideo(localMedia.info.mimeType) -> true MimeTypes.isVideo(localMedia.info.mimeType) -> true
@ -535,7 +533,6 @@ class MessageComposerPresenter @Inject constructor(
mediaSender.sendMedia( mediaSender.sendMedia(
uri = uri, uri = uri,
mimeType = mimeType, mimeType = mimeType,
compressIfPossible = false,
progressCallback = progressCallback progressCallback = progressCallback
).getOrThrow() ).getOrThrow()
} }

6
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt

@ -17,6 +17,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter
import io.element.android.features.messages.impl.attachments.preview.SendActionState import io.element.android.features.messages.impl.attachments.preview.SendActionState
import io.element.android.features.messages.impl.fixtures.aMediaAttachment
import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
@ -26,6 +27,7 @@ import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.mockk.mockk import io.mockk.mockk
@ -120,8 +122,8 @@ class AttachmentsPreviewPresenterTest {
room: MatrixRoom = FakeMatrixRoom() room: MatrixRoom = FakeMatrixRoom()
): AttachmentsPreviewPresenter { ): AttachmentsPreviewPresenter {
return AttachmentsPreviewPresenter( return AttachmentsPreviewPresenter(
attachment = Attachment.Media(localMedia, compressIfPossible = false), attachment = aMediaAttachment(localMedia),
mediaSender = MediaSender(mediaPreProcessor, room) mediaSender = MediaSender(mediaPreProcessor, room, InMemorySessionPreferencesStore())
) )
} }
} }

3
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/MediaAttachmentFixtures.kt

@ -10,7 +10,6 @@ package io.element.android.features.messages.impl.fixtures
import io.element.android.features.messages.impl.attachments.Attachment import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.api.local.LocalMedia
fun aMediaAttachment(localMedia: LocalMedia, compressIfPossible: Boolean = true) = Attachment.Media( fun aMediaAttachment(localMedia: LocalMedia) = Attachment.Media(
localMedia = localMedia, localMedia = localMedia,
compressIfPossible = compressIfPossible,
) )

2
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt

@ -1489,7 +1489,7 @@ class MessageComposerPresenterTest {
featureFlagService, featureFlagService,
sessionPreferencesStore, sessionPreferencesStore,
localMediaFactory, localMediaFactory,
MediaSender(mediaPreProcessor, room), MediaSender(mediaPreProcessor, room, InMemorySessionPreferencesStore()),
snackbarDispatcher, snackbarDispatcher,
analyticsService, analyticsService,
DefaultMessageComposerContext(), DefaultMessageComposerContext(),

3
features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt

@ -30,6 +30,7 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.api.aPermissionsState import io.element.android.libraries.permissions.api.aPermissionsState
import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
@ -61,7 +62,7 @@ class VoiceMessageComposerPresenterTest {
sendMediaResult = sendMediaResult sendMediaResult = sendMediaResult
) )
private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() } private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() }
private val mediaSender = MediaSender(mediaPreProcessor, matrixRoom) private val mediaSender = MediaSender(mediaPreProcessor, matrixRoom, InMemorySessionPreferencesStore())
private val messageComposerContext = FakeMessageComposerContext() private val messageComposerContext = FakeMessageComposerContext()
companion object { companion object {

1
features/preferences/impl/build.gradle.kts

@ -55,6 +55,7 @@ dependencies {
implementation(projects.features.deactivation.api) implementation(projects.features.deactivation.api)
implementation(projects.features.roomlist.api) implementation(projects.features.roomlist.api)
implementation(projects.services.analytics.api) implementation(projects.services.analytics.api)
implementation(projects.services.analytics.compose)
implementation(projects.services.toolbox.api) implementation(projects.services.toolbox.api)
implementation(libs.datetime) implementation(libs.datetime)
implementation(libs.coil.compose) implementation(libs.coil.compose)

1
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 { sealed interface AdvancedSettingsEvents {
data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetDeveloperModeEnabled(val enabled: Boolean) : AdvancedSettingsEvents
data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents data class SetSharePresenceEnabled(val enabled: Boolean) : AdvancedSettingsEvents
data class SetCompressMedia(val compress: Boolean) : AdvancedSettingsEvents
data object ChangeTheme : AdvancedSettingsEvents data object ChangeTheme : AdvancedSettingsEvents
data object CancelChangeTheme : AdvancedSettingsEvents data object CancelChangeTheme : AdvancedSettingsEvents
data class SetTheme(val theme: Theme) : AdvancedSettingsEvents data class SetTheme(val theme: Theme) : AdvancedSettingsEvents

7
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 val isSharePresenceEnabled by sessionPreferencesStore
.isSharePresenceEnabled() .isSharePresenceEnabled()
.collectAsState(initial = true) .collectAsState(initial = true)
val doesCompressMedia by sessionPreferencesStore
.doesCompressMedia()
.collectAsState(initial = false)
val theme by remember { val theme by remember {
appPreferencesStore.getThemeFlow().mapToTheme() appPreferencesStore.getThemeFlow().mapToTheme()
} }
@ -49,6 +52,9 @@ class AdvancedSettingsPresenter @Inject constructor(
is AdvancedSettingsEvents.SetSharePresenceEnabled -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetSharePresenceEnabled -> localCoroutineScope.launch {
sessionPreferencesStore.setSharePresence(event.enabled) sessionPreferencesStore.setSharePresence(event.enabled)
} }
is AdvancedSettingsEvents.SetCompressMedia -> localCoroutineScope.launch {
sessionPreferencesStore.setCompressMedia(event.compress)
}
AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false AdvancedSettingsEvents.CancelChangeTheme -> showChangeThemeDialog = false
AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true AdvancedSettingsEvents.ChangeTheme -> showChangeThemeDialog = true
is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch { is AdvancedSettingsEvents.SetTheme -> localCoroutineScope.launch {
@ -61,6 +67,7 @@ class AdvancedSettingsPresenter @Inject constructor(
return AdvancedSettingsState( return AdvancedSettingsState(
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSharePresenceEnabled, isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia,
theme = theme, theme = theme,
showChangeThemeDialog = showChangeThemeDialog, showChangeThemeDialog = showChangeThemeDialog,
eventSink = { handleEvents(it) } eventSink = { handleEvents(it) }

1
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( data class AdvancedSettingsState(
val isDeveloperModeEnabled: Boolean, val isDeveloperModeEnabled: Boolean,
val isSharePresenceEnabled: Boolean, val isSharePresenceEnabled: Boolean,
val doesCompressMedia: Boolean,
val theme: Theme, val theme: Theme,
val showChangeThemeDialog: Boolean, val showChangeThemeDialog: Boolean,
val eventSink: (AdvancedSettingsEvents) -> Unit val eventSink: (AdvancedSettingsEvents) -> Unit

9
features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsStateProvider.kt

@ -16,18 +16,21 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSett
aAdvancedSettingsState(), aAdvancedSettingsState(),
aAdvancedSettingsState(isDeveloperModeEnabled = true), aAdvancedSettingsState(isDeveloperModeEnabled = true),
aAdvancedSettingsState(showChangeThemeDialog = true), aAdvancedSettingsState(showChangeThemeDialog = true),
aAdvancedSettingsState(isSendPublicReadReceiptsEnabled = true), aAdvancedSettingsState(isSharePresenceEnabled = true),
aAdvancedSettingsState(doesCompressMedia = true),
) )
} }
fun aAdvancedSettingsState( fun aAdvancedSettingsState(
isDeveloperModeEnabled: Boolean = false, isDeveloperModeEnabled: Boolean = false,
isSendPublicReadReceiptsEnabled: Boolean = false, isSharePresenceEnabled: Boolean = false,
doesCompressMedia: Boolean = false,
showChangeThemeDialog: Boolean = false, showChangeThemeDialog: Boolean = false,
eventSink: (AdvancedSettingsEvents) -> Unit = {}, eventSink: (AdvancedSettingsEvents) -> Unit = {},
) = AdvancedSettingsState( ) = AdvancedSettingsState(
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSendPublicReadReceiptsEnabled, isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia,
theme = Theme.System, theme = Theme.System,
showChangeThemeDialog = showChangeThemeDialog, showChangeThemeDialog = showChangeThemeDialog,
eventSink = eventSink eventSink = eventSink

26
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.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter 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.Theme
import io.element.android.compound.theme.themes import io.element.android.compound.theme.themes
import io.element.android.features.preferences.impl.R 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.ListItem
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.ui.strings.CommonStrings 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.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -32,6 +35,7 @@ fun AdvancedSettingsView(
onBackClick: () -> Unit, onBackClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val analyticsService = LocalAnalyticsService.current
PreferencePage( PreferencePage(
modifier = modifier, modifier = modifier,
onBackClick = onBackClick, onBackClick = onBackClick,
@ -72,6 +76,28 @@ fun AdvancedSettingsView(
), ),
onClick = { state.eventSink(AdvancedSettingsEvents.SetSharePresenceEnabled(!state.isSharePresenceEnabled)) } 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) { if (state.showChangeThemeDialog) {

16
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.isDeveloperModeEnabled).isFalse()
assertThat(initialState.showChangeThemeDialog).isFalse() assertThat(initialState.showChangeThemeDialog).isFalse()
assertThat(initialState.isSharePresenceEnabled).isTrue() assertThat(initialState.isSharePresenceEnabled).isTrue()
assertThat(initialState.doesCompressMedia).isFalse()
assertThat(initialState.theme).isEqualTo(Theme.System) 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 @Test
fun `present - change theme`() = runTest { fun `present - change theme`() = runTest {
val presenter = createAdvancedSettingsPresenter() val presenter = createAdvancedSettingsPresenter()

54
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 package io.element.android.features.preferences.impl.advanced
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.test.ext.junit.runners.AndroidJUnit4 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.compound.theme.Theme
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.libraries.ui.strings.CommonStrings 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.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn import io.element.android.tests.testutils.clickOn
@ -91,16 +97,64 @@ class AdvancedSettingsViewTest {
rule.clickOn(R.string.screen_advanced_settings_share_presence) rule.clickOn(R.string.screen_advanced_settings_share_presence)
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true)) eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true))
} }
@Test
fun `clicking on media to enable compression emits the expected event`() {
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
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<AdvancedSettingsEvents>()
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 <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAdvancedSettingsView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAdvancedSettingsView(
state: AdvancedSettingsState, state: AdvancedSettingsState,
analyticsService: AnalyticsService = FakeAnalyticsService(),
onBackClick: () -> Unit = EnsureNeverCalled(), onBackClick: () -> Unit = EnsureNeverCalled(),
) { ) {
setContent { setContent {
CompositionLocalProvider(
LocalAnalyticsService provides analyticsService,
) {
AdvancedSettingsView( AdvancedSettingsView(
state = state, state = state,
onBackClick = onBackClick, onBackClick = onBackClick,
) )
} }
} }
}

1
features/share/impl/build.gradle.kts

@ -49,6 +49,7 @@ dependencies {
testImplementation(libs.androidx.compose.ui.test.junit) testImplementation(libs.androidx.compose.ui.test.junit)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.mediaupload.test)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testReleaseImplementation(libs.androidx.compose.ui.test.manifest) testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
} }

9
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.matrix.api.core.RoomId
import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaSender import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -31,6 +32,7 @@ class SharePresenter @AssistedInject constructor(
private val shareIntentHandler: ShareIntentHandler, private val shareIntentHandler: ShareIntentHandler,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val mediaPreProcessor: MediaPreProcessor, private val mediaPreProcessor: MediaPreProcessor,
private val sessionPreferencesStore: SessionPreferencesStore,
) : Presenter<ShareState> { ) : Presenter<ShareState> {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
@ -71,13 +73,16 @@ class SharePresenter @AssistedInject constructor(
roomIds roomIds
.map { roomId -> .map { roomId ->
val room = matrixClient.getRoom(roomId) ?: return@map false 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 filesToShare
.map { fileToShare -> .map { fileToShare ->
mediaSender.sendMedia( mediaSender.sendMedia(
uri = fileToShare.uri, uri = fileToShare.uri,
mimeType = fileToShare.mimeType, mimeType = fileToShare.mimeType,
compressIfPossible = true,
).isSuccess ).isSuccess
} }
.all { it } .all { it }

4
features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt

@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -154,7 +155,8 @@ class SharePresenterTest {
appCoroutineScope = this, appCoroutineScope = this,
shareIntentHandler = shareIntentHandler, shareIntentHandler = shareIntentHandler,
matrixClient = matrixClient, matrixClient = matrixClient,
mediaPreProcessor = mediaPreProcessor mediaPreProcessor = mediaPreProcessor,
InMemorySessionPreferencesStore(),
) )
} }
} }

2
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" posthog = "com.posthog:posthog-android:3.8.2"
sentry = "io.sentry:sentry-android:7.16.0" sentry = "io.sentry:sentry-android:7.16.0"
# main branch can be tested replacing the version with main-SNAPSHOT # 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 # Emojibase
matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.3.3" matrix_emojibase_bindings = "io.element.android:emojibase-bindings:1.3.3"

2
libraries/mediaupload/api/build.gradle.kts

@ -23,10 +23,12 @@ dependencies {
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.di) implementation(projects.libraries.di)
api(projects.libraries.matrix.api) api(projects.libraries.matrix.api)
api(projects.libraries.preferences.api)
implementation(libs.inject) implementation(libs.inject)
implementation(libs.coroutines.core) implementation(libs.coroutines.core)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.mediaupload.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(libs.test.junit) testImplementation(libs.test.junit)

8
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.core.ProgressCallback
import io.element.android.libraries.matrix.api.media.MediaUploadHandler import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.room.MatrixRoom 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.CancellationException
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.first
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject import javax.inject.Inject
class MediaSender @Inject constructor( class MediaSender @Inject constructor(
private val preProcessor: MediaPreProcessor, private val preProcessor: MediaPreProcessor,
private val room: MatrixRoom, private val room: MatrixRoom,
private val sessionPreferencesStore: SessionPreferencesStore,
) { ) {
private val ongoingUploadJobs = ConcurrentHashMap<Job.Key, MediaUploadHandler>() private val ongoingUploadJobs = ConcurrentHashMap<Job.Key, MediaUploadHandler>()
val hasOngoingMediaUploads get() = ongoingUploadJobs.isNotEmpty() val hasOngoingMediaUploads get() = ongoingUploadJobs.isNotEmpty()
@ -27,11 +30,11 @@ class MediaSender @Inject constructor(
suspend fun sendMedia( suspend fun sendMedia(
uri: Uri, uri: Uri,
mimeType: String, mimeType: String,
compressIfPossible: Boolean,
caption: String? = null, caption: String? = null,
formattedCaption: String? = null, formattedCaption: String? = null,
progressCallback: ProgressCallback? = null progressCallback: ProgressCallback? = null
): Result<Unit> { ): Result<Unit> {
val compressIfPossible = sessionPreferencesStore.doesCompressMedia().first()
return preProcessor return preProcessor
.process( .process(
uri = uri, uri = uri,
@ -49,6 +52,7 @@ class MediaSender @Inject constructor(
} }
.handleSendResult() .handleSendResult()
} }
suspend fun sendVoiceMessage( suspend fun sendVoiceMessage(
uri: Uri, uri: Uri,
mimeType: String, mimeType: String,
@ -60,7 +64,7 @@ class MediaSender @Inject constructor(
uri = uri, uri = uri,
mimeType = mimeType, mimeType = mimeType,
deleteOriginal = true, deleteOriginal = true,
compressIfPossible = false compressIfPossible = false,
) )
.flatMapCatching { info -> .flatMapCatching { info ->
val audioInfo = (info as MediaUploadInfo.Audio).audioInfo val audioInfo = (info as MediaUploadInfo.Audio).audioInfo

18
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.media.FakeMediaUploadHandler
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor 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 io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -33,7 +35,7 @@ class MediaSenderTest {
val sender = aMediaSender(preProcessor) val sender = aMediaSender(preProcessor)
val uri = Uri.parse("content://image.jpg") 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) assertThat(preProcessor.processCallCount).isEqualTo(1)
} }
@ -49,7 +51,7 @@ class MediaSenderTest {
val sender = aMediaSender(room = room) val sender = aMediaSender(room = room)
val uri = Uri.parse("content://image.jpg") 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() sendMediaResult.assertions().isCalledOnce()
} }
@ -61,7 +63,7 @@ class MediaSenderTest {
val sender = aMediaSender(preProcessor) val sender = aMediaSender(preProcessor)
val uri = Uri.parse("content://image.jpg") 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() assertThat(result.exceptionOrNull()).isNotNull()
} }
@ -74,7 +76,7 @@ class MediaSenderTest {
val sender = aMediaSender(room = room) val sender = aMediaSender(room = room)
val uri = Uri.parse("content://image.jpg") 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() assertThat(result.exceptionOrNull()).isNotNull()
} }
@ -88,7 +90,7 @@ class MediaSenderTest {
val sender = aMediaSender(room = room) val sender = aMediaSender(room = room)
val sendJob = launch { val sendJob = launch {
val uri = Uri.parse("content://image.jpg") 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 // Wait until several internal tasks run and the file is being uploaded
advanceTimeBy(3L) advanceTimeBy(3L)
@ -109,8 +111,10 @@ class MediaSenderTest {
private fun aMediaSender( private fun aMediaSender(
preProcessor: MediaPreProcessor = FakeMediaPreProcessor(), preProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
room: MatrixRoom = FakeMatrixRoom(), room: MatrixRoom = FakeMatrixRoom(),
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
) = MediaSender( ) = MediaSender(
preProcessor, preProcessor = preProcessor,
room, room = room,
sessionPreferencesStore = sessionPreferencesStore,
) )
} }

2
libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/impl/ImageCompressor.kt

@ -36,7 +36,7 @@ class ImageCompressor @Inject constructor(
resizeMode: ResizeMode, resizeMode: ResizeMode,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
orientation: Int = ExifInterface.ORIENTATION_UNDEFINED, orientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
desiredQuality: Int = 80, desiredQuality: Int = 78,
): Result<ImageCompressionResult> = withContext(dispatchers.io) { ): Result<ImageCompressionResult> = withContext(dispatchers.io) {
runCatching { runCatching {
val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow() val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow()

2
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) val future = Transcoder.into(tmpFile.path)
.setVideoTrackStrategy( .setVideoTrackStrategy(
DefaultVideoStrategy.Builder() DefaultVideoStrategy.Builder()
.addResizer(AtMostResizer(1920, 1080)) .addResizer(AtMostResizer(720, 480))
.build() .build()
) )
.addDataSource(context, uri) .addDataSource(context, uri)

6
libraries/mediaupload/impl/src/test/kotlin/io/element/android/libraries/mediaupload/impl/AndroidMediaPreProcessorTest.kt

@ -55,8 +55,8 @@ class AndroidMediaPreProcessorTest {
height = 1_178, height = 1_178,
width = 1_818, width = 1_818,
mimetype = MimeTypes.Png, mimetype = MimeTypes.Png,
size = 114_867, size = 109_908,
ThumbnailInfo(height = 294, width = 454, mimetype = "image/jpeg", size = 4567), ThumbnailInfo(height = 294, width = 454, mimetype = "image/jpeg", size = 4484),
thumbnailSource = null, thumbnailSource = null,
blurhash = "K13]7q%zWC00R4of%\$baad" blurhash = "K13]7q%zWC00R4of%\$baad"
) )
@ -84,7 +84,7 @@ class AndroidMediaPreProcessorTest {
height = 1_178, height = 1_178,
width = 1_818, width = 1_818,
mimetype = MimeTypes.Png, mimetype = MimeTypes.Png,
size = 114_867, size = 109_908,
thumbnailInfo = null, thumbnailInfo = null,
thumbnailSource = null, thumbnailSource = null,
blurhash = null, blurhash = null,

3
libraries/preferences/api/src/main/kotlin/io/element/android/libraries/preferences/api/store/SessionPreferencesStore.kt

@ -28,5 +28,8 @@ interface SessionPreferencesStore {
suspend fun setSkipSessionVerification(skip: Boolean) suspend fun setSkipSessionVerification(skip: Boolean)
fun isSessionVerificationSkipped(): Flow<Boolean> fun isSessionVerificationSkipped(): Flow<Boolean>
suspend fun setCompressMedia(compress: Boolean)
fun doesCompressMedia(): Flow<Boolean>
suspend fun clear() suspend fun clear()
} }

4
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 sendTypingNotificationsKey = booleanPreferencesKey("sendTypingNotifications")
private val renderTypingNotificationsKey = booleanPreferencesKey("renderTypingNotifications") private val renderTypingNotificationsKey = booleanPreferencesKey("renderTypingNotifications")
private val skipSessionVerification = booleanPreferencesKey("skipSessionVerification") private val skipSessionVerification = booleanPreferencesKey("skipSessionVerification")
private val compressMedia = booleanPreferencesKey("compressMedia")
private val dataStoreFile = storeFile(context, sessionId) private val dataStoreFile = storeFile(context, sessionId)
private val store = PreferenceDataStoreFactory.create( private val store = PreferenceDataStoreFactory.create(
@ -81,6 +82,9 @@ class DefaultSessionPreferencesStore(
override suspend fun setSkipSessionVerification(skip: Boolean) = update(skipSessionVerification, skip) override suspend fun setSkipSessionVerification(skip: Boolean) = update(skipSessionVerification, skip)
override fun isSessionVerificationSkipped(): Flow<Boolean> = get(skipSessionVerification) { false } override fun isSessionVerificationSkipped(): Flow<Boolean> = get(skipSessionVerification) { false }
override suspend fun setCompressMedia(compress: Boolean) = update(compressMedia, compress)
override fun doesCompressMedia(): Flow<Boolean> = get(compressMedia) { false }
override suspend fun clear() { override suspend fun clear() {
dataStoreFile.safeDelete() dataStoreFile.safeDelete()
} }

6
libraries/preferences/test/src/main/kotlin/io/element/android/libraries/preferences/test/InMemorySessionPreferencesStore.kt

@ -18,6 +18,7 @@ class InMemorySessionPreferencesStore(
isSendTypingNotificationsEnabled: Boolean = true, isSendTypingNotificationsEnabled: Boolean = true,
isRenderTypingNotificationsEnabled: Boolean = true, isRenderTypingNotificationsEnabled: Boolean = true,
isSessionVerificationSkipped: Boolean = false, isSessionVerificationSkipped: Boolean = false,
doesCompressMedia: Boolean = false,
) : SessionPreferencesStore { ) : SessionPreferencesStore {
private val isSharePresenceEnabled = MutableStateFlow(isSharePresenceEnabled) private val isSharePresenceEnabled = MutableStateFlow(isSharePresenceEnabled)
private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled) private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled)
@ -25,6 +26,7 @@ class InMemorySessionPreferencesStore(
private val isSendTypingNotificationsEnabled = MutableStateFlow(isSendTypingNotificationsEnabled) private val isSendTypingNotificationsEnabled = MutableStateFlow(isSendTypingNotificationsEnabled)
private val isRenderTypingNotificationsEnabled = MutableStateFlow(isRenderTypingNotificationsEnabled) private val isRenderTypingNotificationsEnabled = MutableStateFlow(isRenderTypingNotificationsEnabled)
private val isSessionVerificationSkipped = MutableStateFlow(isSessionVerificationSkipped) private val isSessionVerificationSkipped = MutableStateFlow(isSessionVerificationSkipped)
private val doesCompressMedia = MutableStateFlow(doesCompressMedia)
var clearCallCount = 0 var clearCallCount = 0
private set private set
@ -66,6 +68,10 @@ class InMemorySessionPreferencesStore(
return isSessionVerificationSkipped return isSessionVerificationSkipped
} }
override suspend fun setCompressMedia(compress: Boolean) = doesCompressMedia.emit(compress)
override fun doesCompressMedia(): Flow<Boolean> = doesCompressMedia
override suspend fun clear() { override suspend fun clear() {
clearCallCount++ clearCallCount++
isSendPublicReadReceiptsEnabled.tryEmit(true) isSendPublicReadReceiptsEnabled.tryEmit(true)

4
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_0_en.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0c6ec8d30f2fd12c78bf62c9913a68c7a1f6328f0ac9e577317d0abf72e1131c oid sha256:13e7793d8dd6d08e182b128a9b3dac2877557e8bdd220561d36c2ce1650b94ff
size 41788 size 48107

4
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_1_en.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e37410e8e25d6908bbd88db983be7f1c50b4f848763b66c80bf18de97d7f4916 oid sha256:1bac5247c3a4990eb9155a21f72a49059cbaa93288380ba1ab6be2def8b3a6a9
size 41548 size 47876

4
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_2_en.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4d64cb8ce98dfdf2345e12596e4eb3777ea4cde2bd36303d0af6b47614e28f3f oid sha256:11c969c1d04150cef68da64865bade2ea3bfc1aa5f0d790262315131b57d6233
size 31044 size 31702

4
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_3_en.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:69308b3b8b6a7b8a0c032ba6b023a49fcfbb4f05317cbe96e690b4aa3c6992b8 oid sha256:dc1aa9348e470e9d7e7e1e838e18a3403159c2effb49fa359b2b2db97dd81961
size 41621 size 47901

3
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Day_4_en.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:90d1184879c5a34dc27cc942ec3110e14ccd9a90c152b10a1844fde0566d54fe
size 47841

4
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_0_en.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:702a4067afb752978fef59ca45b6d4c871d084953ab19a3d845f0018a894ac6a oid sha256:c2beb4f4f190f6aca2d4da338d980e8611283ffb9bfd2f402482f8ecc629cf22
size 40510 size 46759

4
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_1_en.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1904587ad63098e18e17663a3bac34f2ad24d79967ab44789a1bc1b994805eeb oid sha256:b65acfd3126efc5cd4fe1a929a786468cc18ee371cb4462ef0cf9f6e8963fcea
size 40205 size 46456

4
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_2_en.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:14098a8dc6d7c9d99b6a04710e1385b39839bf18be3c9ddae7ce437a0c1bf64b oid sha256:8e90bbde9aac7e710703e836f293a00b2a5f35447d4d63c9732de21a0f291449
size 28729 size 29336

4
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_3_en.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6a6151f04f2dca8124597c6d144cafe15a51f7fe6638cfed4380a9b6e125bed4 oid sha256:6da0cf1729162fa1745bcb4dd86be06f90d3bcf6bf034e4a64e1ee9119b6cdd5
size 40259 size 46501

3
tests/uitests/src/test/snapshots/images/features.preferences.impl.advanced_AdvancedSettingsView_Night_4_en.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:66109868b1e893bc569d70110e3e587d7a17d777838cfc3e5c3d3189338d924e
size 46423
Loading…
Cancel
Save