change (media preview config) : introduce new apis from sdk

This commit is contained in:
ganfra
2025-06-25 20:38:39 +02:00
parent 3c4738675e
commit 682897cdb6
8 changed files with 227 additions and 0 deletions

View File

@@ -8,6 +8,7 @@
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
@@ -19,6 +20,9 @@ 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
@@ -40,6 +44,7 @@ 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 {
@@ -72,6 +77,7 @@ interface MatrixClient {
fun notificationSettingsService(): NotificationSettingsService
fun encryptionService(): EncryptionService
fun roomDirectoryService(): RoomDirectoryService
fun mediaPreviewService(): MediaPreviewService
suspend fun getCacheSize(): Long
/**
@@ -169,6 +175,7 @@ interface MatrixClient {
* Return true if Livekit Rtc is supported, i.e. if Element Call is available.
*/
suspend fun isLivekitRtcSupported(): Boolean
}
/**

View File

@@ -0,0 +1,16 @@
/*
* 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.matrix.api.media
/**
* Configuration for media preview ie. invite avatars and timeline media.
*/
data class MediaPreviewConfig(
val mediaPreviewValue: MediaPreviewValue,
val hideInviteAvatar: Boolean,
)

View File

@@ -0,0 +1,42 @@
/*
* 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.matrix.api.media
import kotlinx.coroutines.flow.Flow
interface MediaPreviewService {
/**
* Will fetch the media preview config from the server.
*/
suspend fun fetchMediaPreviewConfig(): Result<MediaPreviewConfig?>
/**
* 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
/**
* 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>
}

View File

@@ -26,6 +26,9 @@ 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
@@ -52,6 +55,7 @@ import io.element.android.libraries.matrix.impl.core.toProgressWatcher
import io.element.android.libraries.matrix.impl.encryption.RustEncryptionService
import io.element.android.libraries.matrix.impl.exception.mapClientException
import io.element.android.libraries.matrix.impl.media.RustMediaLoader
import io.element.android.libraries.matrix.impl.media.RustMediaPreviewService
import io.element.android.libraries.matrix.impl.notification.RustNotificationService
import io.element.android.libraries.matrix.impl.notificationsettings.RustNotificationSettingsService
import io.element.android.libraries.matrix.impl.oidc.toRustAction
@@ -107,6 +111,7 @@ 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
@@ -214,6 +219,11 @@ class RustMatrixClient(
innerClient = innerClient,
)
private val mediaPreviewService = RustMediaPreviewService(
innerClient = innerClient,
sessionDispatcher = sessionDispatcher,
)
private var clientDelegateTaskHandle: TaskHandle? = innerClient.setDelegate(sessionDelegate)
private val _userProfile: MutableStateFlow<MatrixUser> = MutableStateFlow(
@@ -507,6 +517,8 @@ class RustMatrixClient(
override fun roomDirectoryService(): RoomDirectoryService = roomDirectoryService
override fun mediaPreviewService(): MediaPreviewService = mediaPreviewService
internal suspend fun destroy() {
innerNotificationClient.close()
@@ -682,6 +694,8 @@ class RustMatrixClient(
innerClient.isLivekitRtcSupported()
}
private suspend fun File.getCacheSize(
includeCryptoDb: Boolean = false,
): Long = withContext(sessionDispatcher) {

View File

@@ -15,6 +15,7 @@ import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClient
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.MediaPreviewService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
@@ -71,4 +72,9 @@ object SessionMatrixModule {
fun providesRoomDirectoryService(matrixClient: MatrixClient): RoomDirectoryService {
return matrixClient.roomDirectoryService()
}
@Provides
fun providesMediaPreviewService(matrixClient: MatrixClient): MediaPreviewService {
return matrixClient.mediaPreviewService()
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.matrix.impl.media
import io.element.android.libraries.core.extensions.runCatchingExceptions
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.impl.util.mxCallbackFlow
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.Client
import org.matrix.rustcomponents.sdk.InviteAvatars
import org.matrix.rustcomponents.sdk.MediaPreviewConfigListener
import org.matrix.rustcomponents.sdk.MediaPreviews
import org.matrix.rustcomponents.sdk.MediaPreviewConfig as RustMediaPreviewConfig
class RustMediaPreviewService(
private val sessionDispatcher: CoroutineDispatcher,
private val innerClient: Client,
) : MediaPreviewService {
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
innerClient.setInviteAvatarsDisplayPolicy(inviteAvatars)
}
}
}
private fun RustMediaPreviewConfig.into(): MediaPreviewConfig {
return MediaPreviewConfig(
mediaPreviewValue = this@into.mediaPreviews.into(),
hideInviteAvatar = inviteAvatars == InviteAvatars.OFF
)
}
private fun Client.getMediaPreviewConfigFlow() = mxCallbackFlow {
subscribeToMediaPreviewConfig(object : MediaPreviewConfigListener {
override fun onChange(mediaPreviewConfig: RustMediaPreviewConfig?) {
trySend(mediaPreviewConfig?.into())
}
})
}
private fun MediaPreviewValue.into(): MediaPreviews {
return when (this) {
MediaPreviewValue.On -> MediaPreviews.ON
MediaPreviewValue.Off -> MediaPreviews.OFF
MediaPreviewValue.Private -> MediaPreviews.PRIVATE
}
}
private fun MediaPreviews.into(): MediaPreviewValue {
return when (this) {
MediaPreviews.ON -> MediaPreviewValue.On
MediaPreviews.OFF -> MediaPreviewValue.Off
MediaPreviews.PRIVATE -> MediaPreviewValue.Private
}
}

View File

@@ -17,7 +17,10 @@ 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
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
@@ -36,6 +39,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader
import io.element.android.libraries.matrix.test.media.FakeMediaPreviewService
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.pushers.FakePushersService
@@ -72,6 +76,7 @@ class FakeMatrixClient(
private val syncService: FakeSyncService = FakeSyncService(),
private val encryptionService: FakeEncryptionService = FakeEncryptionService(),
private val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(),
private val mediaPreviewService: MediaPreviewService = FakeMediaPreviewService(),
private val accountManagementUrlResult: (AccountManagementAction?) -> Result<String?> = { lambdaError() },
private val resolveRoomAliasResult: (RoomAlias) -> Result<Optional<ResolvedRoomAlias>> = {
Result.success(
@@ -91,6 +96,7 @@ 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
@@ -234,11 +240,13 @@ class FakeMatrixClient(
override fun notificationService(): NotificationService = notificationService
override fun notificationSettingsService(): NotificationSettingsService = notificationSettingsService
override fun encryptionService(): EncryptionService = encryptionService
override fun mediaPreviewService(): MediaPreviewService = mediaPreviewService
override fun roomMembershipObserver(): RoomMembershipObserver {
return RoomMembershipObserver()
}
// Mocks
fun givenCreateRoomResult(result: Result<RoomId>) {

View File

@@ -0,0 +1,50 @@
/*
* 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.matrix.test.media
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.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
class FakeMediaPreviewService(
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 {
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)
}
override suspend fun setHideInviteAvatars(hide: Boolean): Result<Unit> = simulateLongTask {
setHideInviteAvatarsResult(hide)
}
}