Add media file limit size warning and media quality selection (#5131)
* Add `VideoCompressorPreset` enum This represents the different compression presets used for processing videos before uploading them * Add `VideoCompressorHelper` util class to calculate the scaled output size of the video given an input size and its optimal bitrate Also add `MediaOptimizationConfig` which will be used to decide how to apply compression in `MediaPreProcessor` * Add `RustMatrixClient.getMaxFileUploadSize()` function and `MaxUploadSizeProvider` so we can import only this functionality into other components * Try preloading the max file upload size the first time we get network connectivity - it's a best effort This should help ensure we'll have this value available later, even if we still need to load it asynchronously. * Split the `compressMedia` preference into `compressImages` and `compressMediaPreset` * Modify the media processing parts to use the new classes and utils * Add `MediaOptimizationSelectorPresenter`, which will retrieve the compression values and the max file upload size, also estimating the compressed video file sizes if needed. * Add a feature flag to allow selecting the media upload quality per upload * Integrate the previous changes with the attachments preview screen Add strings from localazy too. * Adapt the rest of the app calls to upload media to using the media optimization configs * Allow modifying the default compression values in advanced settings, based on the feature flag value * Pass the `fileSize` in `MediaUploadInfo` too, to be able to check it against the `maxUploadSize` * Update screenshots --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
ffe183c952
commit
a170d80cb3
174 changed files with 2152 additions and 340 deletions
|
|
@ -28,8 +28,11 @@ interface SessionPreferencesStore {
|
|||
suspend fun setSkipSessionVerification(skip: Boolean)
|
||||
fun isSessionVerificationSkipped(): Flow<Boolean>
|
||||
|
||||
suspend fun setCompressMedia(compress: Boolean)
|
||||
fun doesCompressMedia(): Flow<Boolean>
|
||||
suspend fun setOptimizeImages(compress: Boolean)
|
||||
fun doesOptimizeImages(): Flow<Boolean>
|
||||
|
||||
suspend fun setVideoCompressionPreset(preset: VideoCompressionPreset)
|
||||
fun getVideoCompressionPreset(): Flow<VideoCompressionPreset>
|
||||
|
||||
suspend fun clear()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.preferences.api.store
|
||||
|
||||
/**
|
||||
* Video compression presets to use when processing videos before uploading them.
|
||||
*/
|
||||
enum class VideoCompressionPreset {
|
||||
/** High quality compression, suitable for high-resolution videos. */
|
||||
HIGH,
|
||||
|
||||
/** Standard quality compression, suitable for most videos. */
|
||||
STANDARD,
|
||||
|
||||
/** Low quality compression, suitable for low-resolution videos or when bandwidth is a concern. */
|
||||
LOW
|
||||
}
|
||||
|
|
@ -12,12 +12,15 @@ import androidx.datastore.preferences.core.PreferenceDataStoreFactory
|
|||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import androidx.datastore.preferences.preferencesDataStoreFile
|
||||
import io.element.android.libraries.androidutils.file.safeDelete
|
||||
import io.element.android.libraries.androidutils.hash.hash
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
|
@ -41,7 +44,8 @@ class DefaultSessionPreferencesStore(
|
|||
private val sendTypingNotificationsKey = booleanPreferencesKey("sendTypingNotifications")
|
||||
private val renderTypingNotificationsKey = booleanPreferencesKey("renderTypingNotifications")
|
||||
private val skipSessionVerification = booleanPreferencesKey("skipSessionVerification")
|
||||
private val compressMedia = booleanPreferencesKey("compressMedia")
|
||||
private val compressImages = booleanPreferencesKey("compressMedia")
|
||||
private val compressMediaPreset = stringPreferencesKey("compressMediaPreset")
|
||||
|
||||
private val dataStoreFile = storeFile(context, sessionId)
|
||||
private val store = PreferenceDataStoreFactory.create(
|
||||
|
|
@ -82,8 +86,12 @@ class DefaultSessionPreferencesStore(
|
|||
override suspend fun setSkipSessionVerification(skip: Boolean) = update(skipSessionVerification, skip)
|
||||
override fun isSessionVerificationSkipped(): Flow<Boolean> = get(skipSessionVerification) { false }
|
||||
|
||||
override suspend fun setCompressMedia(compress: Boolean) = update(compressMedia, compress)
|
||||
override fun doesCompressMedia(): Flow<Boolean> = get(compressMedia) { true }
|
||||
override suspend fun setOptimizeImages(compress: Boolean) = update(compressImages, compress)
|
||||
override fun doesOptimizeImages(): Flow<Boolean> = get(compressImages) { true }
|
||||
|
||||
override suspend fun setVideoCompressionPreset(preset: VideoCompressionPreset) = update(compressMediaPreset, preset.name)
|
||||
override fun getVideoCompressionPreset(): Flow<VideoCompressionPreset> = get(compressMediaPreset) { VideoCompressionPreset.STANDARD.name }
|
||||
.map { tryOrNull { VideoCompressionPreset.valueOf(it) } ?: VideoCompressionPreset.STANDARD }
|
||||
|
||||
override suspend fun clear() {
|
||||
dataStoreFile.safeDelete()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.libraries.preferences.test
|
||||
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ class InMemorySessionPreferencesStore(
|
|||
isRenderTypingNotificationsEnabled: Boolean = true,
|
||||
isSessionVerificationSkipped: Boolean = false,
|
||||
doesCompressMedia: Boolean = true,
|
||||
videoCompressionPreset: VideoCompressionPreset = VideoCompressionPreset.STANDARD,
|
||||
) : SessionPreferencesStore {
|
||||
private val isSharePresenceEnabled = MutableStateFlow(isSharePresenceEnabled)
|
||||
private val isSendPublicReadReceiptsEnabled = MutableStateFlow(isSendPublicReadReceiptsEnabled)
|
||||
|
|
@ -27,6 +29,7 @@ class InMemorySessionPreferencesStore(
|
|||
private val isRenderTypingNotificationsEnabled = MutableStateFlow(isRenderTypingNotificationsEnabled)
|
||||
private val isSessionVerificationSkipped = MutableStateFlow(isSessionVerificationSkipped)
|
||||
private val doesCompressMedia = MutableStateFlow(doesCompressMedia)
|
||||
private val videoCompressionPreset = MutableStateFlow(videoCompressionPreset)
|
||||
var clearCallCount = 0
|
||||
private set
|
||||
|
||||
|
|
@ -68,9 +71,17 @@ class InMemorySessionPreferencesStore(
|
|||
return isSessionVerificationSkipped
|
||||
}
|
||||
|
||||
override suspend fun setCompressMedia(compress: Boolean) = doesCompressMedia.emit(compress)
|
||||
override suspend fun setOptimizeImages(compress: Boolean) = doesCompressMedia.emit(compress)
|
||||
|
||||
override fun doesCompressMedia(): Flow<Boolean> = doesCompressMedia
|
||||
override fun doesOptimizeImages(): Flow<Boolean> = doesCompressMedia
|
||||
|
||||
override suspend fun setVideoCompressionPreset(preset: VideoCompressionPreset) {
|
||||
videoCompressionPreset.value = preset
|
||||
}
|
||||
|
||||
override fun getVideoCompressionPreset(): Flow<VideoCompressionPreset> {
|
||||
return videoCompressionPreset
|
||||
}
|
||||
|
||||
override suspend fun clear() {
|
||||
clearCallCount++
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue