Skip to content

Commit

Permalink
Add media upload setting.
Browse files Browse the repository at this point in the history
Compress media regarding the settings.
Image compression change quality to 78%
Video compression change size to 720 x 48
  • Loading branch information
bmarty committed Oct 28, 2024
1 parent 01e7986 commit 846dbc2
Show file tree
Hide file tree
Showing 22 changed files with 162 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ fun anAttachmentsPreviewState(
) = AttachmentsPreviewState(
attachment = Attachment.Media(
localMedia = LocalMedia("file://path".toUri(), mediaInfo),
compressIfPossible = true
),
sendActionState = sendActionState,
eventSink = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -294,7 +294,6 @@ class MessageComposerPresenter @Inject constructor(
name = null,
formattedFileSize = null
),
compressIfPossible = true
),
attachmentState = attachmentsState,
)
Expand Down Expand Up @@ -493,7 +492,6 @@ class MessageComposerPresenter @Inject constructor(
attachmentsState: MutableState<AttachmentsState>,
uri: Uri?,
mimeType: String? = null,
compressIfPossible: Boolean = true,
) {
if (uri == null) {
attachmentsState.value = AttachmentsState.None
Expand All @@ -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
Expand Down Expand Up @@ -535,7 +533,6 @@ class MessageComposerPresenter @Inject constructor(
mediaSender.sendMedia(
uri = uri,
mimeType = mimeType,
compressIfPossible = false,
progressCallback = progressCallback
).getOrThrow()
}
Expand Down
1 change: 1 addition & 0 deletions features/preferences/impl/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand All @@ -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 {
Expand All @@ -61,6 +67,7 @@ class AdvancedSettingsPresenter @Inject constructor(
return AdvancedSettingsState(
isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia,
theme = theme,
showChangeThemeDialog = showChangeThemeDialog,
eventSink = { handleEvents(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,21 @@ open class AdvancedSettingsStateProvider : PreviewParameterProvider<AdvancedSett
aAdvancedSettingsState(),
aAdvancedSettingsState(isDeveloperModeEnabled = true),
aAdvancedSettingsState(showChangeThemeDialog = true),
aAdvancedSettingsState(isSendPublicReadReceiptsEnabled = true),
aAdvancedSettingsState(isSharePresenceEnabled = true),
aAdvancedSettingsState(doesCompressMedia = true),
)
}

fun aAdvancedSettingsState(
isDeveloperModeEnabled: Boolean = false,
isSendPublicReadReceiptsEnabled: Boolean = false,
isSharePresenceEnabled: Boolean = false,
doesCompressMedia: Boolean = false,
showChangeThemeDialog: Boolean = false,
eventSink: (AdvancedSettingsEvents) -> Unit = {},
) = AdvancedSettingsState(
isDeveloperModeEnabled = isDeveloperModeEnabled,
isSharePresenceEnabled = isSendPublicReadReceiptsEnabled,
isSharePresenceEnabled = isSharePresenceEnabled,
doesCompressMedia = doesCompressMedia,
theme = Theme.System,
showChangeThemeDialog = showChangeThemeDialog,
eventSink = eventSink
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -32,6 +35,7 @@ fun AdvancedSettingsView(
onBackClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val analyticsService = LocalAnalyticsService.current
PreferencePage(
modifier = modifier,
onBackClick = onBackClick,
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<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(
state: AdvancedSettingsState,
analyticsService: AnalyticsService = FakeAnalyticsService(),
onBackClick: () -> Unit = EnsureNeverCalled(),
) {
setContent {
AdvancedSettingsView(
state = state,
onBackClick = onBackClick,
)
CompositionLocalProvider(
LocalAnalyticsService provides analyticsService,
) {
AdvancedSettingsView(
state = state,
onBackClick = onBackClick,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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<ShareState> {
@AssistedFactory
interface Factory {
Expand Down Expand Up @@ -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 }
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions libraries/mediaupload/api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 846dbc2

Please sign in to comment.