change (media preview config) : final refactoring and tests

This commit is contained in:
ganfra 2025-06-30 21:31:58 +02:00
parent 4b4cfa341e
commit ca46166c67
27 changed files with 676 additions and 165 deletions

View file

@ -8,7 +8,6 @@
package io.element.android.libraries.matrix.api
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.MatrixPatterns
import io.element.android.libraries.matrix.api.core.ProgressCallback
@ -20,9 +19,7 @@ import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaPreviewConfig
import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
@ -44,7 +41,6 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.withContext
import java.util.Optional
interface MatrixClient {
@ -175,7 +171,6 @@ interface MatrixClient {
* Return true if Livekit Rtc is supported, i.e. if Element Call is available.
*/
suspend fun isLivekitRtcSupported(): Boolean
}
/**

View file

@ -13,4 +13,14 @@ package io.element.android.libraries.matrix.api.media
data class MediaPreviewConfig(
val mediaPreviewValue: MediaPreviewValue,
val hideInviteAvatar: Boolean,
)
) {
companion object {
/**
* The default config if unknown (no local nor server config).
*/
val DEFAULT = MediaPreviewConfig(
mediaPreviewValue = MediaPreviewValue.On,
hideInviteAvatar = false
)
}
}

View file

@ -7,7 +7,7 @@
package io.element.android.libraries.matrix.api.media
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
interface MediaPreviewService {
/**
@ -19,24 +19,17 @@ interface MediaPreviewService {
* Will emit the media preview config known by the client.
* This will emit a new value when received from sync.
*/
fun getMediaPreviewConfigFlow(): Flow<MediaPreviewConfig?>
/**
* Get the media preview display policy from the cache. This value is updated through sync.
*/
suspend fun getMediaPreviewValue(): MediaPreviewValue?
/**
* Get the invite avatars display policy from the cache. This value is updated through sync.
*/
suspend fun getHideInviteAvatars(): Boolean
val mediaPreviewConfigFlow: StateFlow<MediaPreviewConfig>
/**
* Set the media preview display policy. This will update the value on the server and update the local value when successful.
*/
suspend fun setMediaPreviewValue(mediaPreviewValue: MediaPreviewValue): Result<Unit>
/**
* Set the invite avatars display policy. This will update the value on the server and update the local value when successful.
*/
suspend fun setHideInviteAvatars(hide: Boolean): Result<Unit>
}
fun MediaPreviewService.getMediaPreviewValue() = mediaPreviewConfigFlow.value.mediaPreviewValue

View file

@ -26,9 +26,7 @@ import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.createroom.RoomPreset
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaPreviewConfig
import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
@ -111,7 +109,6 @@ import org.matrix.rustcomponents.sdk.AuthDataPasswordDetails
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.ClientException
import org.matrix.rustcomponents.sdk.IgnoredUsersListener
import org.matrix.rustcomponents.sdk.InviteAvatars
import org.matrix.rustcomponents.sdk.NotificationProcessSetup
import org.matrix.rustcomponents.sdk.PowerLevels
import org.matrix.rustcomponents.sdk.RoomInfoListener
@ -220,6 +217,7 @@ class RustMatrixClient(
)
private val mediaPreviewService = RustMediaPreviewService(
sessionCoroutineScope = sessionCoroutineScope,
innerClient = innerClient,
sessionDispatcher = sessionDispatcher,
)
@ -694,8 +692,6 @@ class RustMatrixClient(
innerClient.isLivekitRtcSupported()
}
private suspend fun File.getCacheSize(
includeCryptoDb: Boolean = false,
): Long = withContext(sessionDispatcher) {

View file

@ -13,7 +13,10 @@ import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.matrix.impl.util.mxCallbackFlow
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.InviteAvatars
@ -22,27 +25,27 @@ import org.matrix.rustcomponents.sdk.MediaPreviews
import org.matrix.rustcomponents.sdk.MediaPreviewConfig as RustMediaPreviewConfig
class RustMediaPreviewService(
sessionCoroutineScope: CoroutineScope,
private val sessionDispatcher: CoroutineDispatcher,
private val innerClient: Client,
) : MediaPreviewService {
override val mediaPreviewConfigFlow: StateFlow<MediaPreviewConfig> =
innerClient
.getMediaPreviewConfigFlow()
.stateIn(sessionCoroutineScope, started = SharingStarted.Lazily, initialValue = MediaPreviewConfig.DEFAULT)
override suspend fun fetchMediaPreviewConfig(): Result<MediaPreviewConfig?> = withContext(sessionDispatcher) {
runCatchingExceptions {
innerClient.fetchMediaPreviewConfig()?.into()
}
}
override fun getMediaPreviewConfigFlow(): Flow<MediaPreviewConfig?> = innerClient.getMediaPreviewConfigFlow()
override suspend fun getMediaPreviewValue(): MediaPreviewValue? = innerClient.getMediaPreviewDisplayPolicy()?.into()
override suspend fun setMediaPreviewValue(mediaPreviewValue: MediaPreviewValue): Result<Unit> = withContext(sessionDispatcher) {
runCatchingExceptions {
innerClient.setMediaPreviewDisplayPolicy(mediaPreviewValue.into())
}
}
override suspend fun getHideInviteAvatars(): Boolean = innerClient.getInviteAvatarsDisplayPolicy() == InviteAvatars.OFF
override suspend fun setHideInviteAvatars(hide: Boolean): Result<Unit> = withContext(sessionDispatcher) {
runCatchingExceptions {
val inviteAvatars = if (hide) InviteAvatars.OFF else InviteAvatars.ON
@ -61,7 +64,9 @@ private fun RustMediaPreviewConfig.into(): MediaPreviewConfig {
private fun Client.getMediaPreviewConfigFlow() = mxCallbackFlow {
subscribeToMediaPreviewConfig(object : MediaPreviewConfigListener {
override fun onChange(mediaPreviewConfig: RustMediaPreviewConfig?) {
trySend(mediaPreviewConfig?.into())
if (mediaPreviewConfig != null) {
trySend(mediaPreviewConfig.into())
}
}
})
}
@ -81,4 +86,3 @@ private fun MediaPreviews.into(): MediaPreviewValue {
MediaPreviews.PRIVATE -> MediaPreviewValue.Private
}
}

View file

@ -17,8 +17,6 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.media.MediaPreviewConfig
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.notification.NotificationService
@ -96,7 +94,6 @@ class FakeMatrixClient(
private val canReportRoomLambda: () -> Boolean = { false },
private val isLivekitRtcSupportedLambda: () -> Boolean = { false },
override val ignoredUsersFlow: StateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf()),
) : MatrixClient {
var setDisplayNameCalled: Boolean = false
private set
@ -246,7 +243,6 @@ class FakeMatrixClient(
return RoomMembershipObserver()
}
// Mocks
fun givenCreateRoomResult(result: Result<RoomId>) {

View file

@ -12,34 +12,19 @@ import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class FakeMediaPreviewService(
override val mediaPreviewConfigFlow: StateFlow<MediaPreviewConfig> = MutableStateFlow(MediaPreviewConfig.DEFAULT),
private val fetchMediaPreviewConfigResult: () -> Result<MediaPreviewConfig?> = { lambdaError() },
private val mediaPreviewConfigFlow: Flow<MediaPreviewConfig?> = flowOf(null),
private val getMediaPreviewValue: ()-> MediaPreviewValue? = { null },
private val getHideInviteAvatars: () -> Boolean = { false },
private val setMediaPreviewValueResult: (MediaPreviewValue) -> Result<Unit> = { lambdaError() },
private val setHideInviteAvatarsResult: (Boolean) -> Result<Unit> = { lambdaError() },
): MediaPreviewService {
) : MediaPreviewService {
override suspend fun fetchMediaPreviewConfig(): Result<MediaPreviewConfig?> = simulateLongTask {
fetchMediaPreviewConfigResult()
}
override fun getMediaPreviewConfigFlow(): Flow<MediaPreviewConfig?> {
return mediaPreviewConfigFlow
}
override suspend fun getMediaPreviewValue(): MediaPreviewValue? = simulateLongTask {
getMediaPreviewValue.invoke()
}
override suspend fun getHideInviteAvatars(): Boolean = simulateLongTask {
getHideInviteAvatars.invoke()
}
override suspend fun setMediaPreviewValue(mediaPreviewValue: MediaPreviewValue): Result<Unit> = simulateLongTask {
setMediaPreviewValueResult(mediaPreviewValue)
}

View file

@ -22,9 +22,13 @@ interface AppPreferencesStore {
suspend fun setTheme(theme: String)
fun getThemeFlow(): Flow<String?>
@Deprecated("Use MediaPreviewService instead. Kept only for migration.")
suspend fun setHideInviteAvatars(hide: Boolean?)
@Deprecated("Use MediaPreviewService instead. Kept only for migration.")
fun getHideInviteAvatarsFlow(): Flow<Boolean?>
@Deprecated("Use MediaPreviewService instead. Kept only for migration.")
suspend fun setTimelineMediaPreviewValue(mediaPreviewValue: MediaPreviewValue?)
@Deprecated("Use MediaPreviewService instead. Kept only for migration.")
fun getTimelineMediaPreviewValueFlow(): Flow<MediaPreviewValue?>
suspend fun setTracingLogLevel(logLevel: LogLevel)

View file

@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.matrix.api.media.getMediaPreviewValue
import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser

View file

@ -43,8 +43,6 @@ import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
import io.element.android.libraries.matrix.test.notification.aNotificationData
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationMediaRepo
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent