From eb3666a93ba0d23a139611aff4d53592b97d8551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 11 Mar 2025 17:14:54 +0100 Subject: [PATCH 01/10] Update SDK version and fix breaking changes --- .../impl/utils/DefaultCallWidgetProvider.kt | 3 ++- .../ConfirmAccountProviderPresenter.kt | 2 +- .../roomdetails/impl/RoomDetailsPresenter.kt | 5 +++-- .../RoomNotificationSettingsPresenter.kt | 8 ++++--- .../SecurityAndPrivacyPresenter.kt | 4 +++- gradle/libs.versions.toml | 2 +- .../libraries/matrix/api/auth/OidcPrompt.kt | 22 ------------------- .../libraries/matrix/api/room/MatrixRoom.kt | 4 +++- .../matrix/api/room/MatrixRoomInfo.kt | 1 + .../libraries/matrix/impl/auth/OidcPrompt.kt | 5 +---- .../matrix/impl/room/MatrixRoomInfoMapper.kt | 6 +++++ .../matrix/impl/room/RustMatrixRoom.kt | 18 +++++++++++++-- .../ObserveRoomMemberIdentityStateChange.kt | 4 ++-- 13 files changed, 44 insertions(+), 40 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt index 097e8a65317..70c1916a575 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt @@ -38,7 +38,8 @@ class DefaultCallWidgetProvider @Inject constructor( val baseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull() ?: elementCallBaseUrlProvider.provides(matrixClient) ?: ElementCallConfig.DEFAULT_BASE_URL - val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = room.isEncrypted) + val isEncrypted = room.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() + val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = isEncrypted) val callUrl = room.generateWidgetWebViewUrl( widgetSettings = widgetSettings, clientId = clientId, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt index 359e556e696..eb5a8fee671 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt @@ -93,7 +93,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( val matrixHomeServerDetails = authenticationService.getHomeserverDetails().value!! if (matrixHomeServerDetails.supportsOidcLogin) { // Retrieve the details right now - val oidcPrompt = if (params.isAccountCreation) OidcPrompt.Create else OidcPrompt.Consent + val oidcPrompt = if (params.isAccountCreation) OidcPrompt.Create else OidcPrompt.Login LoginFlow.OidcFlow(authenticationService.getOidcUrl(oidcPrompt).getOrThrow()) } else if (params.isAccountCreation) { val url = webClientUrlForAuthenticationRetriever.retrieve(homeserverUrl) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 631158f3bba..ebac7a14fe8 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -100,6 +100,7 @@ class RoomDetailsPresenter @Inject constructor( val membersState by room.membersStateFlow.collectAsState() val canInvite by getCanInvite(membersState) + val isEncrypted by remember { derivedStateOf { roomInfo?.isEncrypted == true } } val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME) val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR) val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC) @@ -140,7 +141,7 @@ class RoomDetailsPresenter @Inject constructor( } RoomDetailsEvent.UnmuteNotification -> { scope.launch(dispatchers.io) { - client.notificationSettingsService().unmuteRoom(room.roomId, room.isEncrypted, room.isOneToOne) + client.notificationSettingsService().unmuteRoom(room.roomId, isEncrypted, room.isOneToOne) } } is RoomDetailsEvent.SetFavorite -> scope.setFavorite(event.isFavorite) @@ -169,7 +170,7 @@ class RoomDetailsPresenter @Inject constructor( roomAvatarUrl = roomAvatar, roomTopic = topicState, memberCount = room.joinedMemberCount, - isEncrypted = room.isEncrypted, + isEncrypted = isEncrypted, canInvite = canInvite, canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room, canShowNotificationSettings = canShowNotificationSettings.value, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index 77bdafe94a8..fb726e755ef 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -82,7 +82,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( getDefaultRoomNotificationMode(defaultRoomNotificationMode) fetchNotificationSettings(pendingRoomNotificationMode, roomNotificationSettings) observeNotificationSettings(pendingRoomNotificationMode, roomNotificationSettings) - shouldDisplayMentionsOnlyDisclaimer = room.isEncrypted && !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) + shouldDisplayMentionsOnlyDisclaimer = room.isEncrypted == true && !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) } fun handleEvents(event: RoomNotificationSettingsEvents) { @@ -143,16 +143,18 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( roomNotificationSettings: MutableState> ) = launch { suspend { + val isEncrypted = room.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() pendingModeState.value = null - notificationSettingsService.getRoomNotificationSettings(room.roomId, room.isEncrypted, room.isOneToOne).getOrThrow() + notificationSettingsService.getRoomNotificationSettings(room.roomId, isEncrypted, room.isOneToOne).getOrThrow() }.runCatchingUpdatingState(roomNotificationSettings) } private fun CoroutineScope.getDefaultRoomNotificationMode( defaultRoomNotificationMode: MutableState ) = launch { + val isEncrypted = room.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() defaultRoomNotificationMode.value = notificationSettingsService.getDefaultRoomNotificationMode( - room.isEncrypted, + isEncrypted, room.isOneToOne ).getOrThrow() } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt index 769ff87baee..396e462b7e4 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt @@ -59,6 +59,8 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor( val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val roomInfo = room.roomInfoFlow.collectAsState(null) + val isEncrypted by remember { derivedStateOf { roomInfo.value?.isEncrypted == true }} + val savedIsVisibleInRoomDirectory = remember { mutableStateOf>(AsyncData.Uninitialized) } LaunchedEffect(Unit) { isRoomVisibleInRoomDirectory(savedIsVisibleInRoomDirectory) @@ -68,7 +70,7 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor( derivedStateOf { SecurityAndPrivacySettings( roomAccess = roomInfo.value?.joinRule.map(), - isEncrypted = room.isEncrypted, + isEncrypted = isEncrypted, isVisibleInRoomDirectory = savedIsVisibleInRoomDirectory.value, historyVisibility = roomInfo.value?.historyVisibility.map(), address = roomInfo.value?.firstDisplayableAlias(homeserverName)?.value, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 619d0b77dfb..4ff83f06bba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -174,7 +174,7 @@ jsoup = "org.jsoup:jsoup:1.19.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.3.6" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.3.11" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcPrompt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcPrompt.kt index 86c9e4e825b..79576e0adb8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcPrompt.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/auth/OidcPrompt.kt @@ -8,34 +8,12 @@ package io.element.android.libraries.matrix.api.auth sealed interface OidcPrompt { - /** - * The Authorization Server must not display any authentication or consent - * user interface pages. - */ - data object None : OidcPrompt - /** * The Authorization Server should prompt the End-User for * reauthentication. */ data object Login : OidcPrompt - /** - * The Authorization Server should prompt the End-User for consent before - * returning information to the Client. - */ - data object Consent : OidcPrompt - - /** - * The Authorization Server should prompt the End-User to select a user - * account. - * - * This enables an End-User who has multiple accounts at the Authorization - * Server to select amongst the multiple accounts that they might have - * current sessions for. - */ - data object SelectAccount : OidcPrompt - /** * The Authorization Server should prompt the End-User to create a user * account. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 41b25c0dc0e..3f85f8f247c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -50,7 +50,7 @@ interface MatrixRoom : Closeable { val alternativeAliases: List val topic: String? val avatarUrl: String? - val isEncrypted: Boolean + val isEncrypted: Boolean? val isSpace: Boolean val isDirect: Boolean val isPublic: Boolean @@ -453,4 +453,6 @@ interface MatrixRoom : Closeable { * Update the join rule for this room. */ suspend fun updateJoinRule(joinRule: JoinRule): Result + + suspend fun getUpdatedIsEncrypted(): Result } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt index 32cc4646c09..8d1c730bb3b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt @@ -28,6 +28,7 @@ data class MatrixRoomInfo( val topic: String?, val avatarUrl: String?, val isDirect: Boolean, + val isEncrypted: Boolean?, val joinRule: JoinRule?, val isSpace: Boolean, val isTombstoned: Boolean, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcPrompt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcPrompt.kt index ea1eb93b293..0ba9eb4c498 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcPrompt.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/auth/OidcPrompt.kt @@ -12,10 +12,7 @@ import org.matrix.rustcomponents.sdk.OidcPrompt as RustOidcPrompt internal fun OidcPrompt.toRustPrompt(): RustOidcPrompt { return when (this) { - OidcPrompt.None -> RustOidcPrompt.None - OidcPrompt.Login -> RustOidcPrompt.Login - OidcPrompt.Consent -> RustOidcPrompt.Consent - OidcPrompt.SelectAccount -> RustOidcPrompt.SelectAccount + OidcPrompt.Login -> RustOidcPrompt.Unknown("consent") OidcPrompt.Create -> RustOidcPrompt.Create is OidcPrompt.Unknown -> RustOidcPrompt.Unknown(value) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt index 5b39595e203..b6343644bcf 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt @@ -23,6 +23,7 @@ import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentMap import org.matrix.rustcomponents.sdk.Membership import org.matrix.rustcomponents.sdk.RoomHero +import uniffi.matrix_sdk_base.EncryptionState import org.matrix.rustcomponents.sdk.Membership as RustMembership import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode @@ -37,6 +38,11 @@ class MatrixRoomInfoMapper { topic = it.topic, avatarUrl = it.avatarUrl, isDirect = it.isDirect, + isEncrypted = when (it.encryptionState) { + EncryptionState.ENCRYPTED -> true + EncryptionState.NOT_ENCRYPTED -> false + EncryptionState.UNKNOWN -> null + }, joinRule = it.joinRule?.map(), isSpace = it.isSpace, isTombstoned = it.isTombstoned, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index d44e8a3c3a2..ac43239fee6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -101,6 +101,7 @@ import org.matrix.rustcomponents.sdk.getElementCallRequiredPermissions import org.matrix.rustcomponents.sdk.use import timber.log.Timber import uniffi.matrix_sdk.RoomPowerLevelChanges +import uniffi.matrix_sdk_base.EncryptionState import java.io.File import kotlin.coroutines.cancellation.CancellationException import org.matrix.rustcomponents.sdk.IdentityStatusChange as RustIdentityStateChange @@ -316,8 +317,14 @@ class RustMatrixRoom( override val avatarUrl: String? get() = runCatching { roomListItem.avatarUrl() ?: innerRoom.avatarUrl() }.getOrDefault(null) - override val isEncrypted: Boolean - get() = runCatching { innerRoom.isEncrypted() }.getOrDefault(false) + override val isEncrypted: Boolean? + get() = runCatching { + when (innerRoom.encryptionState()) { + EncryptionState.ENCRYPTED -> true + EncryptionState.NOT_ENCRYPTED -> false + EncryptionState.UNKNOWN -> null + } + }.getOrNull() override val canonicalAlias: RoomAlias? get() = runCatching { innerRoom.canonicalAlias()?.let(::RoomAlias) }.getOrDefault(null) @@ -377,6 +384,7 @@ class RustMatrixRoom( val currentRoomNotificationSettings = currentState.roomNotificationSettings() _roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings) runCatching { + val isEncrypted = isEncrypted ?: getUpdatedIsEncrypted().getOrThrow() notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow() }.map { _roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(it) @@ -866,6 +874,12 @@ class RustMatrixRoom( } } + override suspend fun getUpdatedIsEncrypted(): Result = withContext(roomDispatcher) { + runCatching { + innerRoom.latestEncryptionState() == EncryptionState.ENCRYPTED + } + } + private fun createTimeline( timeline: InnerTimeline, mode: Timeline.Mode, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChange.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChange.kt index 2549bba2d9a..7610b55b20d 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChange.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/ObserveRoomMemberIdentityStateChange.kt @@ -30,10 +30,10 @@ import kotlinx.coroutines.flow.onEach @OptIn(ExperimentalCoroutinesApi::class) fun MatrixRoom.roomMemberIdentityStateChange(): Flow> { - return syncUpdateFlow + return roomInfoFlow .filter { // Room cannot become unencrypted, so we can just apply a filter here. - isEncrypted + it.isEncrypted == true } .distinctUntilChanged() .flatMapLatest { From d56b9e3ba0a3b1a28fca9bb64383cd0d800050ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 13 Mar 2025 12:13:49 +0100 Subject: [PATCH 02/10] Fix more breaking changes --- .../element/android/x/ElementXApplication.kt | 4 +- .../io/element/android/x/di/AppBindings.kt | 3 ++ ...gInitializer.kt => PlatformInitializer.kt} | 5 ++- .../libraries/core/coroutine/SuspendLazy.kt | 25 +++++++++++ .../api/platform/InitPlatformService.kt | 21 ++++++++++ .../item/event/TimelineItemEventOrigin.kt | 3 +- .../matrix/api/tracing/TracingService.kt | 1 - .../libraries/matrix/impl/RustMatrixClient.kt | 20 +++++++-- .../RustNotificationSettingsService.kt | 41 ++++++++++--------- .../impl/platform/RustInitPlatformService.kt | 26 ++++++++++++ .../matrix/impl/room/RustMatrixRoom.kt | 3 +- .../item/event/EventTimelineItemMapper.kt | 1 + .../matrix/impl/tracing/RustTracingService.kt | 18 ++++---- 13 files changed, 130 insertions(+), 41 deletions(-) rename app/src/main/kotlin/io/element/android/x/initializer/{TracingInitializer.kt => PlatformInitializer.kt} (93%) create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/SuspendLazy.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/platform/InitPlatformService.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/platform/RustInitPlatformService.kt diff --git a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt index 4f4e360f4fb..a4bfe0c60d7 100644 --- a/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt +++ b/app/src/main/kotlin/io/element/android/x/ElementXApplication.kt @@ -15,7 +15,7 @@ import io.element.android.x.di.AppComponent import io.element.android.x.di.DaggerAppComponent import io.element.android.x.info.logApplicationInfo import io.element.android.x.initializer.CrashInitializer -import io.element.android.x.initializer.TracingInitializer +import io.element.android.x.initializer.PlatformInitializer class ElementXApplication : Application(), DaggerComponentOwner { override val daggerComponent: AppComponent = DaggerAppComponent.factory().create(this) @@ -24,7 +24,7 @@ class ElementXApplication : Application(), DaggerComponentOwner { super.onCreate() AppInitializer.getInstance(this).apply { initializeComponent(CrashInitializer::class.java) - initializeComponent(TracingInitializer::class.java) + initializeComponent(PlatformInitializer::class.java) initializeComponent(CacheCleanerInitializer::class.java) } logApplicationInfo(this) diff --git a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt index 4a7e12863e6..8525f6356b2 100644 --- a/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt +++ b/app/src/main/kotlin/io/element/android/x/di/AppBindings.kt @@ -17,6 +17,7 @@ import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.di.AppScope import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.matrix.api.platform.InitPlatformService import io.element.android.libraries.matrix.api.tracing.TracingService import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.services.analytics.api.AnalyticsService @@ -27,6 +28,8 @@ interface AppBindings { fun tracingService(): TracingService + fun platformService(): InitPlatformService + fun bugReporter(): BugReporter fun lockScreenService(): LockScreenService diff --git a/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt b/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt similarity index 93% rename from app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt rename to app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt index ac225db2b0c..650480a0eee 100644 --- a/app/src/main/kotlin/io/element/android/x/initializer/TracingInitializer.kt +++ b/app/src/main/kotlin/io/element/android/x/initializer/PlatformInitializer.kt @@ -22,10 +22,11 @@ import timber.log.Timber private const val ELEMENT_X_TARGET = "elementx" -class TracingInitializer : Initializer { +class PlatformInitializer : Initializer { override fun create(context: Context) { val appBindings = context.bindings() val tracingService = appBindings.tracingService() + val platformService = appBindings.platformService() val bugReporter = appBindings.bugReporter() Timber.plant(tracingService.createTimberTree(ELEMENT_X_TARGET)) val preferencesStore = appBindings.preferencesStore() @@ -38,7 +39,7 @@ class TracingInitializer : Initializer { extraTargets = listOf(ELEMENT_X_TARGET), ) bugReporter.setCurrentTracingLogLevel(logLevel.name) - tracingService.setupTracing(tracingConfiguration) + platformService.init(tracingConfiguration) // Also set env variable for rust back trace Os.setenv("RUST_BACKTRACE", "1", true) } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/SuspendLazy.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/SuspendLazy.kt new file mode 100644 index 00000000000..e5442c49407 --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/coroutine/SuspendLazy.kt @@ -0,0 +1,25 @@ +/* + * 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.core.coroutine + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.launch +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +fun suspendLazy(coroutineContext: CoroutineContext = EmptyCoroutineContext, block: suspend () -> T): Lazy> { + return lazy(LazyThreadSafetyMode.NONE) { + val deferred = CompletableDeferred() + CoroutineScope(coroutineContext).launch { + deferred.complete(block()) + } + deferred + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/platform/InitPlatformService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/platform/InitPlatformService.kt new file mode 100644 index 00000000000..6199d1704d2 --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/platform/InitPlatformService.kt @@ -0,0 +1,21 @@ +/* + * 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.platform + +import io.element.android.libraries.matrix.api.tracing.TracingConfiguration + +/** + * This service is responsible for initializing the platform-related settings of the SDK. + */ +interface InitPlatformService { + /** + * Initialize the platform-related settings of the SDK. + * @param tracingConfiguration the tracing configuration to use for logging. + */ + fun init(tracingConfiguration: TracingConfiguration) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt index 10835f04482..528e28d5cf4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/TimelineItemEventOrigin.kt @@ -10,5 +10,6 @@ package io.element.android.libraries.matrix.api.timeline.item.event enum class TimelineItemEventOrigin { LOCAL, SYNC, - PAGINATION + PAGINATION, + CACHE, } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt index 53ee37ca78e..5cef6cde02b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/tracing/TracingService.kt @@ -10,6 +10,5 @@ package io.element.android.libraries.matrix.api.tracing import timber.log.Timber interface TracingService { - fun setupTracing(tracingConfiguration: TracingConfiguration) fun createTimberTree(target: String): Timber.Tree } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 1f08be27b39..cbcd67296ac 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -151,8 +151,7 @@ class RustMatrixClient( private val notificationProcessSetup = NotificationProcessSetup.SingleProcess(innerSyncService) private val innerNotificationClient = runBlocking { innerClient.notificationClient(notificationProcessSetup) } private val notificationService = RustNotificationService(innerNotificationClient, dispatchers, clock) - private val notificationSettingsService = RustNotificationSettingsService(innerClient, dispatchers) - .apply { start() } + private val notificationSettingsService = RustNotificationSettingsService(innerClient, sessionCoroutineScope, dispatchers) private val encryptionService = RustEncryptionService( client = innerClient, syncService = rustSyncService, @@ -216,7 +215,7 @@ class RustMatrixClient( userId = sessionId, // TODO cache for displayName? displayName = null, - avatarUrl = innerClient.cachedAvatarUrl(), + avatarUrl = null, ) ) @@ -237,8 +236,21 @@ class RustMatrixClient( sessionDelegate.bindClient(this) sessionCoroutineScope.launch { + // Start notification settings + notificationSettingsService.start() + // Force a refresh of the profile getUserProfile() + + // And emit a new value + _userProfile.emit( + MatrixUser( + userId = sessionId, + // TODO cache for displayName? + displayName = null, + avatarUrl = innerClient.cachedAvatarUrl(), + ) + ) } } @@ -479,10 +491,10 @@ class RustMatrixClient( appCoroutineScope.launch { roomFactory.destroy() rustSyncService.destroy() + notificationSettingsService.destroy() } sessionCoroutineScope.cancel() clientDelegateTaskHandle?.cancelAndDestroy() - notificationSettingsService.destroy() verificationService.destroy() sessionDelegate.clearCurrentClient() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt index b08a6675a45..5cfd9741ddc 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsService.kt @@ -8,10 +8,12 @@ package io.element.android.libraries.matrix.impl.notificationsettings import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.core.coroutine.suspendLazy import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationSettings +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -24,9 +26,10 @@ import timber.log.Timber class RustNotificationSettingsService( client: Client, + sessionCoroutineScope: CoroutineScope, private val dispatchers: CoroutineDispatchers, ) : NotificationSettingsService { - private val notificationSettings = client.getNotificationSettings() + private val notificationSettings by suspendLazy(sessionCoroutineScope.coroutineContext + dispatchers.io) { client.getNotificationSettings() } private val _notificationSettingsChangeFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) override val notificationSettingsChangeFlow: SharedFlow = _notificationSettingsChangeFlow.asSharedFlow() @@ -36,22 +39,22 @@ class RustNotificationSettingsService( } } - fun start() { - notificationSettings.setDelegate(notificationSettingsDelegate) + suspend fun start() { + notificationSettings.await().setDelegate(notificationSettingsDelegate) } - fun destroy() { - notificationSettings.setDelegate(null) + suspend fun destroy() { + notificationSettings.await().setDelegate(null) } override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result = runCatching { - notificationSettings.getRoomNotificationSettings(roomId.value, isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::map) + notificationSettings.await().getRoomNotificationSettings(roomId.value, isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::map) } override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, isOneToOne: Boolean): Result = runCatching { - notificationSettings.getDefaultRoomNotificationMode(isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::mapMode) + notificationSettings.await().getDefaultRoomNotificationMode(isEncrypted, isOneToOne).let(RoomNotificationSettingsMapper::mapMode) } override suspend fun setDefaultRoomNotificationMode( @@ -61,7 +64,7 @@ class RustNotificationSettingsService( ): Result = withContext(dispatchers.io) { runCatching { try { - notificationSettings.setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode)) + notificationSettings.await().setDefaultRoomNotificationMode(isEncrypted, isOneToOne, mode.let(RoomNotificationSettingsMapper::mapMode)) } catch (exception: NotificationSettingsException.RuleNotFound) { // `setDefaultRoomNotificationMode` updates multiple rules including unstable rules (e.g. the polls push rules defined in the MSC3930) // since production home servers may not have these rules yet, we drop the RuleNotFound error @@ -72,13 +75,13 @@ class RustNotificationSettingsService( override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.setRoomNotificationMode(roomId.value, mode.let(RoomNotificationSettingsMapper::mapMode)) + notificationSettings.await().setRoomNotificationMode(roomId.value, mode.let(RoomNotificationSettingsMapper::mapMode)) } } override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.restoreDefaultRoomNotificationMode(roomId.value) + notificationSettings.await().restoreDefaultRoomNotificationMode(roomId.value) } } @@ -86,53 +89,53 @@ class RustNotificationSettingsService( override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean) = withContext(dispatchers.io) { runCatching { - notificationSettings.unmuteRoom(roomId.value, isEncrypted, isOneToOne) + notificationSettings.await().unmuteRoom(roomId.value, isEncrypted, isOneToOne) } } override suspend fun isRoomMentionEnabled(): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.isRoomMentionEnabled() + notificationSettings.await().isRoomMentionEnabled() } } override suspend fun setRoomMentionEnabled(enabled: Boolean): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.setRoomMentionEnabled(enabled) + notificationSettings.await().setRoomMentionEnabled(enabled) } } override suspend fun isCallEnabled(): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.isCallEnabled() + notificationSettings.await().isCallEnabled() } } override suspend fun setCallEnabled(enabled: Boolean): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.setCallEnabled(enabled) + notificationSettings.await().setCallEnabled(enabled) } } override suspend fun isInviteForMeEnabled(): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.isInviteForMeEnabled() + notificationSettings.await().isInviteForMeEnabled() } } override suspend fun setInviteForMeEnabled(enabled: Boolean): Result = withContext(dispatchers.io) { runCatching { - notificationSettings.setInviteForMeEnabled(enabled) + notificationSettings.await().setInviteForMeEnabled(enabled) } } override suspend fun getRoomsWithUserDefinedRules(): Result> = runCatching { - notificationSettings.getRoomsWithUserDefinedRules(enabled = true) + notificationSettings.await().getRoomsWithUserDefinedRules(enabled = true) } override suspend fun canHomeServerPushEncryptedEventsToDevice(): Result = runCatching { - notificationSettings.canPushEncryptedEventToDevice() + notificationSettings.await().canPushEncryptedEventToDevice() } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/platform/RustInitPlatformService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/platform/RustInitPlatformService.kt new file mode 100644 index 00000000000..1324ebe7965 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/platform/RustInitPlatformService.kt @@ -0,0 +1,26 @@ +/* + * 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.platform + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.AppScope +import io.element.android.libraries.matrix.api.platform.InitPlatformService +import io.element.android.libraries.matrix.api.tracing.TracingConfiguration +import io.element.android.libraries.matrix.impl.tracing.map +import org.matrix.rustcomponents.sdk.initPlatform +import javax.inject.Inject + +@ContributesBinding(AppScope::class) +class RustInitPlatformService @Inject constructor() : InitPlatformService { + override fun init(tracingConfiguration: TracingConfiguration) { + initPlatform( + config = tracingConfiguration.map(), + useLightweightTokioRuntime = false + ) + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index ac43239fee6..5a6b0671774 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -82,6 +82,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener @@ -339,7 +340,7 @@ class RustMatrixRoom( get() = runCatching { innerRoom.isSpace() }.getOrDefault(false) override val isDirect: Boolean - get() = runCatching { innerRoom.isDirect() }.getOrDefault(false) + get() = runCatching { runBlocking { innerRoom.isDirect() } }.getOrDefault(false) override val joinedMemberCount: Long get() = runCatching { innerRoom.joinedMembersCount().toLong() }.getOrDefault(0) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index 3e2817bf9ba..89661b5888e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -152,6 +152,7 @@ private fun RustEventItemOrigin.map(): TimelineItemEventOrigin { RustEventItemOrigin.LOCAL -> TimelineItemEventOrigin.LOCAL RustEventItemOrigin.SYNC -> TimelineItemEventOrigin.SYNC RustEventItemOrigin.PAGINATION -> TimelineItemEventOrigin.PAGINATION + RustEventItemOrigin.CACHE -> TimelineItemEventOrigin.CACHE } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt index 74fb2e9db10..0638e499747 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/tracing/RustTracingService.kt @@ -20,17 +20,6 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class RustTracingService @Inject constructor(private val buildMeta: BuildMeta) : TracingService { - override fun setupTracing(tracingConfiguration: TracingConfiguration) { - val rustTracingConfiguration = org.matrix.rustcomponents.sdk.TracingConfiguration( - writeToStdoutOrSystem = tracingConfiguration.writesToLogcat, - logLevel = tracingConfiguration.logLevel.toRustLogLevel(), - extraTargets = tracingConfiguration.extraTargets, - writeToFiles = tracingConfiguration.writesToFilesConfiguration.toTracingFileConfiguration(), - ) - org.matrix.rustcomponents.sdk.setupTracing(rustTracingConfiguration) - Timber.d("setupTracing: $rustTracingConfiguration") - } - override fun createTimberTree(target: String): Timber.Tree { return RustTracingTree(target = target, retrieveFromStackTrace = buildMeta.isDebuggable) } @@ -57,3 +46,10 @@ private fun WriteToFilesConfiguration.toTracingFileConfiguration(): TracingFileC ) } } + +fun TracingConfiguration.map(): org.matrix.rustcomponents.sdk.TracingConfiguration = org.matrix.rustcomponents.sdk.TracingConfiguration( + writeToStdoutOrSystem = writesToLogcat, + logLevel = logLevel.toRustLogLevel(), + extraTargets = extraTargets, + writeToFiles = writesToFilesConfiguration.toTracingFileConfiguration(), +) From 33ab0094452f121d8849e3a79cc0624c198a5a83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 13 Mar 2025 13:04:27 +0100 Subject: [PATCH 03/10] Fix test fixtures --- .../android/libraries/matrix/test/room/FakeMatrixRoom.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 04c32b5445d..522346fbab2 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -616,6 +616,10 @@ class FakeMatrixRoom( updateJoinRuleResult(joinRule) } + override suspend fun getUpdatedIsEncrypted(): Result = simulateLongTask { + Result.success(isEncrypted) + } + fun givenRoomMembersState(state: MatrixRoomMembersState) { membersStateFlow.value = state } From 40504199acf8afd2ecfd70d42d7e502601361aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 13 Mar 2025 15:42:30 +0100 Subject: [PATCH 04/10] Use RoomInfo for `isDirect`, `isEncrypted` --- .../leaveroom/impl/LeaveRoomPresenter.kt | 8 ++-- .../leaveroom/impl/LeaveRoomPresenterTest.kt | 17 ++++++-- .../features/messages/impl/MessagesNode.kt | 5 ++- .../messages/impl/MessagesPresenter.kt | 5 ++- .../MessageComposerPresenter.kt | 2 +- .../list/PinnedMessagesListPresenter.kt | 8 ++-- .../impl/timeline/TimelinePresenter.kt | 7 ++-- .../messages/impl/MessagesPresenterTest.kt | 24 ++++++++---- .../IdentityChangeStatePresenterTest.kt | 13 +++++-- .../MessageComposerPresenterTest.kt | 9 ++++- .../roomdetails/impl/RoomDetailsPresenter.kt | 4 +- .../RoomMembersModerationPresenter.kt | 2 +- .../RoomNotificationSettingsPresenter.kt | 11 +++++- .../SecurityAndPrivacyPresenter.kt | 2 +- .../impl/RoomDetailsPresenterTest.kt | 28 ++++++++++--- .../SecurityAndPrivacyPresenterTest.kt | 1 + .../matrix/api/analytics/ViewRoomExt.kt | 5 ++- .../matrix/api/room/MatrixRoomInfo.kt | 39 +++++++++++++++++++ .../matrix/api/room/RoomIsDmCheck.kt | 6 ++- .../api/room/recent/RecentDirectRoom.kt | 2 +- .../libraries/matrix/impl/RustMatrixClient.kt | 10 ----- .../matrix/impl/analytics/JoinedRoomExt.kt | 11 ++++-- .../matrix/impl/room/MatrixRoomInfoMapper.kt | 1 + .../matrix/impl/room/RustMatrixRoom.kt | 4 +- .../matrix/impl/timeline/RustTimeline.kt | 7 ++-- .../impl/RustMatrixClientFactoryTest.kt | 2 +- .../matrix/impl/RustMatrixClientTest.kt | 2 +- .../impl/analytics/JoinedRoomExtKtTest.kt | 28 ++++++++----- .../impl/fixtures/factories/RoomInfo.kt | 3 ++ .../impl/fixtures/fakes/FakeRustClient.kt | 4 +- .../RustNotificationSettingsServiceTest.kt | 1 + .../impl/room/MatrixRoomInfoMapperTest.kt | 7 ++++ .../impl/room/join/DefaultJoinRoomTest.kt | 13 +++++-- .../matrix/test/room/RoomInfoFixture.kt | 4 ++ .../matrix/test/room/RoomSummaryFixture.kt | 4 ++ .../matrix/ui/room/MatrixRoomMembers.kt | 5 ++- .../matrix/ui/room/MatrixRoomState.kt | 6 +-- .../matrix/ui/room/MatrixRoomMembersTest.kt | 36 +++++++++++++---- .../NotificationBroadcastReceiverHandler.kt | 2 +- ...otificationBroadcastReceiverHandlerTest.kt | 19 ++++++++- 40 files changed, 271 insertions(+), 96 deletions(-) diff --git a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenter.kt b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenter.kt index a567b2d3d75..4943e751850 100644 --- a/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenter.kt +++ b/features/leaveroom/impl/src/main/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenter.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.isDm +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject @@ -74,10 +75,11 @@ private suspend fun showLeaveRoomAlert( confirmation: MutableState, ) { matrixClient.getRoom(roomId)?.use { room -> + val roomInfo = room.roomInfoFlow.first() confirmation.value = when { - room.isDm -> Dm(roomId) - !room.isPublic -> PrivateRoom(roomId) - room.joinedMemberCount == 1L -> LastUserInRoom(roomId) + roomInfo.isDm -> Dm(roomId) + !roomInfo.isPublic -> PrivateRoom(roomId) + roomInfo.joinedMembersCount == 1L -> LastUserInRoom(roomId) else -> Generic(roomId) } } diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterTest.kt index e9d229ad934..22407c93d42 100644 --- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterTest.kt +++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterTest.kt @@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.assert import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -52,7 +53,9 @@ class LeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom() + result = FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo(isDirect = false, isPublic = true, joinedMembersCount = 10)) + } ) } ) @@ -72,7 +75,9 @@ class LeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom(isPublic = false), + result = FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo(isPublic = false)) + }, ) } ) @@ -92,7 +97,9 @@ class LeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom(joinedMemberCount = 1), + result = FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo(joinedMembersCount = 1)) + }, ) } ) @@ -112,7 +119,9 @@ class LeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom(activeMemberCount = 2, isDirect = true), + result = FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo(isDirect = true, activeMembersCount = 2)) + }, ) } ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index a8076d023c7..4ea6a1c9d3c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -59,11 +59,14 @@ import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugIn import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.services.analytics.api.AnalyticsService import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch @ContributesNode(RoomScope::class) class MessagesNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, + private val coroutineScope: CoroutineScope, private val room: MatrixRoom, private val analyticsService: AnalyticsService, messageComposerPresenterFactory: MessageComposerPresenter.Factory, @@ -108,7 +111,7 @@ class MessagesNode @AssistedInject constructor( super.onBuilt() lifecycle.subscribe( onCreate = { - analyticsService.capture(room.toAnalyticsViewRoom()) + coroutineScope.launch { analyticsService.capture(room.toAnalyticsViewRoom()) } }, onDestroy = { mediaPlayer.close() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 8a7c15a3ec5..6a6e144afcf 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -169,9 +169,10 @@ class MessagesPresenter @AssistedInject constructor( val inviteProgress = remember { mutableStateOf>(AsyncData.Uninitialized) } var showReinvitePrompt by remember { mutableStateOf(false) } - LaunchedEffect(hasDismissedInviteDialog, composerState.textEditorState.hasFocus(), syncUpdateFlow.value) { + val composerHasFocus by remember { derivedStateOf { composerState.textEditorState.hasFocus() } } + LaunchedEffect(hasDismissedInviteDialog, composerHasFocus, roomInfo) { withContext(dispatchers.io) { - showReinvitePrompt = !hasDismissedInviteDialog && composerState.textEditorState.hasFocus() && room.isDm && room.activeMemberCount == 1L + showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo?.isDm == true && roomInfo?.activeMembersCount == 1L } } val isOnline by syncService.isOnline().collectAsState() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt index f3a44559bc1..fce8e732e4d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenter.kt @@ -202,7 +202,7 @@ class MessageComposerPresenter @AssistedInject constructor( suspend fun canSendRoomMention(): Boolean { val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false) - return !room.isDm && userCanSendAtRoom + return !room.isDm() && userCanSendAtRoom } // This will trigger a search immediately when `@` is typed diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt index d00e9e7d1af..947563d4cf3 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -38,11 +38,11 @@ import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.powerlevels.canPinUnpin import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOther import io.element.android.libraries.matrix.api.room.powerlevels.canRedactOwn import io.element.android.libraries.matrix.api.room.roomMembers +import io.element.android.libraries.matrix.ui.room.isDmAsState import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction @@ -85,9 +85,11 @@ class PinnedMessagesListPresenter @AssistedInject constructor( @Composable override fun present(): PinnedMessagesListState { - val timelineRoomInfo = remember { + val isDm by room.isDmAsState() + + val timelineRoomInfo = remember(isDm) { TimelineRoomInfo( - isDm = room.isDm, + isDm = isDm, name = room.displayName, // We don't need to compute those values userHasPermissionToSendMessage = false, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 8a496859d0f..f522ee48415 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -34,6 +34,7 @@ import io.element.android.features.poll.api.actions.EndPollAction import io.element.android.features.poll.api.actions.SendPollResponseAction import io.element.android.features.roomcall.api.RoomCallState import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UniqueId @@ -231,11 +232,11 @@ class TimelinePresenter @AssistedInject constructor( val typingNotificationState = typingNotificationPresenter.present() val roomCallState = roomCallStatePresenter.present() - val timelineRoomInfo by remember(typingNotificationState, roomCallState) { + val timelineRoomInfo by remember(typingNotificationState, roomCallState, roomInfo) { derivedStateOf { TimelineRoomInfo( - name = room.displayName, - isDm = room.isDm, + name = roomInfo?.name ?: room.displayName, + isDm = roomInfo?.isDm.orFalse(), userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendReaction = userHasPermissionToSendReaction, roomCallState = roomCallState, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index a0ddc6bbc93..03905c6860c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -95,6 +95,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -536,15 +537,15 @@ class MessagesPresenterTest { fun `present - shows prompt to reinvite users in DM`() = runTest { val room = FakeMatrixRoom( sessionId = A_SESSION_ID, - isDirect = true, - activeMemberCount = 1L, canUserSendMessageResult = { _, _ -> Result.success(true) }, canRedactOwnResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) }, canUserJoinCallResult = { Result.success(true) }, typingNoticeResult = { Result.success(Unit) }, canUserPinUnpinResult = { Result.success(true) }, - ) + ).apply { + givenRoomInfo(aRoomInfo(isDirect = true, joinedMembersCount = 1, activeMembersCount = 1)) + } val presenter = createMessagesPresenter(matrixRoom = room) presenter.testWithLifecycleOwner { val initialState = awaitItem() @@ -552,7 +553,8 @@ class MessagesPresenterTest { assertThat(initialState.showReinvitePrompt).isFalse() // When the input field is focused we show the alert (initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true - skipItems(1) + // Skip intermediate states + skipItems(2) val focusedState = awaitItem() assertThat(focusedState.showReinvitePrompt).isTrue() // If it's dismissed then we stop showing the alert @@ -567,20 +569,22 @@ class MessagesPresenterTest { fun `present - doesn't show reinvite prompt in non-direct room`() = runTest { val room = FakeMatrixRoom( sessionId = A_SESSION_ID, - isDirect = false, - activeMemberCount = 1L, canUserSendMessageResult = { _, _ -> Result.success(true) }, canRedactOwnResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) }, canUserJoinCallResult = { Result.success(true) }, typingNoticeResult = { Result.success(Unit) }, canUserPinUnpinResult = { Result.success(true) }, - ) + ).apply { + givenRoomInfo(aRoomInfo(isDirect = false, joinedMembersCount = 1, activeMembersCount = 1)) + } val presenter = createMessagesPresenter(matrixRoom = room) presenter.testWithLifecycleOwner { val initialState = awaitItem() assertThat(initialState.showReinvitePrompt).isFalse() (initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true + // Skip intermediate events + skipItems(1) val focusedState = awaitItem() assertThat(focusedState.showReinvitePrompt).isFalse() } @@ -598,12 +602,16 @@ class MessagesPresenterTest { canUserJoinCallResult = { Result.success(true) }, typingNoticeResult = { Result.success(Unit) }, canUserPinUnpinResult = { Result.success(true) }, - ) + ).apply { + givenRoomInfo(aRoomInfo(isDirect = true, joinedMembersCount = 2, activeMembersCount = 2)) + } val presenter = createMessagesPresenter(matrixRoom = room) presenter.testWithLifecycleOwner { val initialState = awaitItem() assertThat(initialState.showReinvitePrompt).isFalse() (initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true + // Skip intermediate events + skipItems(1) val focusedState = awaitItem() assertThat(focusedState.showReinvitePrompt).isFalse() } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt index 29f64b831f6..2bbf6b7c245 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -44,7 +45,9 @@ class IdentityChangeStatePresenterTest { @Test fun `present - when the room emits identity change, the presenter emits new state`() = runTest { - val room = FakeMatrixRoom(isEncrypted = true) + val room = FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo(isEncrypted = true)) + } val presenter = createIdentityChangeStatePresenter(room) presenter.test { val initialState = awaitItem() @@ -85,8 +88,9 @@ class IdentityChangeStatePresenterTest { ) // No item emitted. expectNoEvents() - // Room become encrypted. - room.enableEncryption() + // Room becomes encrypted. + room.givenRoomInfo(aRoomInfo(isEncrypted = true)) + val finalItem = awaitItem() assertThat(finalItem.roomMemberIdentityStateChanges).hasSize(1) val value = finalItem.roomMemberIdentityStateChanges.first() @@ -99,7 +103,7 @@ class IdentityChangeStatePresenterTest { @Test fun `present - when the room emits identity change, the presenter emits new state with member details`() = runTest { - val room = FakeMatrixRoom(isEncrypted = true).apply { + val room = FakeMatrixRoom().apply { givenRoomMembersState( MatrixRoomMembersState.Ready( listOf( @@ -110,6 +114,7 @@ class IdentityChangeStatePresenterTest { ).toImmutableList() ) ) + givenRoomInfo(aRoomInfo(isEncrypted = true)) } val presenter = createIdentityChangeStatePresenter(room) presenter.test { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index b7ba51192bd..6f7bcd31e58 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -63,6 +63,7 @@ import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache @@ -998,7 +999,6 @@ class MessageComposerPresenterTest { val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) var canUserTriggerRoomNotificationResult = true val room = FakeMatrixRoom( - isDirect = false, canUserTriggerRoomNotificationResult = { Result.success(canUserTriggerRoomNotificationResult) }, typingNoticeResult = { Result.success(Unit) } ).apply { @@ -1007,6 +1007,7 @@ class MessageComposerPresenterTest { persistentListOf(currentUser, invitedUser, bob, david), ) ) + givenRoomInfo(aRoomInfo(isDirect = false)) } val flagsService = FakeFeatureFlagService( mapOf( @@ -1071,6 +1072,12 @@ class MessageComposerPresenterTest { persistentListOf(currentUser, invitedUser, bob, david), ) ) + givenRoomInfo( + aRoomInfo( + isDirect = true, + activeMembersCount = 2, + ) + ) } val flagsService = FakeFeatureFlagService( mapOf( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index ebac7a14fe8..14bf391c44d 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -44,6 +44,7 @@ import io.element.android.libraries.matrix.api.room.roomNotificationSettings import io.element.android.libraries.matrix.ui.room.canHandleKnockRequestsAsState import io.element.android.libraries.matrix.ui.room.getCurrentRoomMember import io.element.android.libraries.matrix.ui.room.getDirectRoomMember +import io.element.android.libraries.matrix.ui.room.isDmAsState import io.element.android.libraries.matrix.ui.room.isOwnUserAdmin import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange import io.element.android.services.analytics.api.AnalyticsService @@ -101,6 +102,7 @@ class RoomDetailsPresenter @Inject constructor( val canInvite by getCanInvite(membersState) val isEncrypted by remember { derivedStateOf { roomInfo?.isEncrypted == true } } + val isDm by room.isDmAsState() val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME) val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR) val canEditTopic by getCanSendState(membersState, StateEventType.ROOM_TOPIC) @@ -180,7 +182,7 @@ class RoomDetailsPresenter @Inject constructor( leaveRoomState = leaveRoomState, roomNotificationSettings = roomNotificationSettingsState.roomNotificationSettings(), isFavorite = isFavorite, - displayRolesAndPermissionsSettings = !room.isDm && isUserAdmin, + displayRolesAndPermissionsSettings = !isDm && isUserAdmin, isPublic = joinRule == JoinRule.Public, heroes = roomInfo?.heroes.orEmpty().toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt index cbd9d3aca08..e9d4a915143 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenter.kt @@ -50,7 +50,7 @@ class RoomMembersModerationPresenter @Inject constructor( val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canBan by room.canBanAsState(syncUpdateFlow.value) val canKick by room.canKickAsState(syncUpdateFlow.value) - val isDm by room.isDmAsState(syncUpdateFlow.value) + val isDm by room.isDmAsState() val currentUserMemberPowerLevel by room.userPowerLevelAsState(syncUpdateFlow.value) val canDisplayModerationActions by remember { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index fb726e755ef..8f3dae9f3d4 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable @@ -78,11 +79,19 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( mutableStateOf(null) } + val isRoomEncrypted by produceState(room.isEncrypted) { + room.roomInfoFlow.collect { value = it.isEncrypted } + } + LaunchedEffect(Unit) { getDefaultRoomNotificationMode(defaultRoomNotificationMode) fetchNotificationSettings(pendingRoomNotificationMode, roomNotificationSettings) observeNotificationSettings(pendingRoomNotificationMode, roomNotificationSettings) - shouldDisplayMentionsOnlyDisclaimer = room.isEncrypted == true && !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) + } + + LaunchedEffect(isRoomEncrypted) { + shouldDisplayMentionsOnlyDisclaimer = isRoomEncrypted == true + && !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) } fun handleEvents(event: RoomNotificationSettingsEvents) { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt index 396e462b7e4..4a2c67d9146 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt @@ -59,7 +59,7 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor( val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val roomInfo = room.roomInfoFlow.collectAsState(null) - val isEncrypted by remember { derivedStateOf { roomInfo.value?.isEncrypted == true }} + val isEncrypted by remember { derivedStateOf { roomInfo.value?.isEncrypted == true } } val savedIsVisibleInRoomDirectory = remember { mutableStateOf>(AsyncData.Uninitialized) } LaunchedEffect(Unit) { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt index 610843b7c61..53799c7c926 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt @@ -125,7 +125,6 @@ class RoomDetailsPresenterTest { assertThat(initialState.roomAvatarUrl).isEqualTo(room.avatarUrl) assertThat(initialState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.topic!!)) assertThat(initialState.memberCount).isEqualTo(room.joinedMemberCount) - assertThat(initialState.isEncrypted).isEqualTo(room.isEncrypted) assertThat(initialState.canShowPinnedMessages).isTrue() assertThat(initialState.pinnedMessagesCount).isNull() assertThat(initialState.canShowSecurityAndPrivacy).isFalse() @@ -181,8 +180,6 @@ class RoomDetailsPresenterTest { val myRoomMember = aRoomMember(A_SESSION_ID) val otherRoomMember = aRoomMember(A_USER_ID_2) val room = aMatrixRoom( - isEncrypted = true, - isDirect = true, canInviteResult = { Result.success(true) }, canUserJoinCallResult = { Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) }, @@ -196,6 +193,13 @@ class RoomDetailsPresenterTest { ).apply { val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) + + givenRoomInfo( + aRoomInfo( + isEncrypted = true, + isDirect = true, + ) + ) } val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -287,8 +291,6 @@ class RoomDetailsPresenterTest { val myRoomMember = aRoomMember(A_SESSION_ID) val otherRoomMember = aRoomMember(A_USER_ID_2) val room = aMatrixRoom( - isEncrypted = true, - isDirect = true, canSendStateResult = { _, stateEventType -> when (stateEventType) { StateEventType.ROOM_TOPIC, @@ -309,6 +311,13 @@ class RoomDetailsPresenterTest { ).apply { val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) + + givenRoomInfo( + aRoomInfo( + isEncrypted = true, + isDirect = true, + ) + ) } val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { @@ -329,7 +338,6 @@ class RoomDetailsPresenterTest { val myRoomMember = aRoomMember(A_SESSION_ID) val otherRoomMember = aRoomMember(A_USER_ID_2) val room = aMatrixRoom( - isEncrypted = true, isDirect = true, topic = null, canSendStateResult = { _, stateEventType -> @@ -352,6 +360,14 @@ class RoomDetailsPresenterTest { ).apply { val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) + + givenRoomInfo( + aRoomInfo( + isDirect = true, + activeMembersCount = 2, + topic = null, + ) + ) } val presenter = createRoomDetailsPresenter(room) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt index 1d94c720111..11e3332f3f1 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt @@ -245,6 +245,7 @@ class SecurityAndPrivacyPresenterTest { aRoomInfo( joinRule = JoinRule.Public, historyVisibility = RoomHistoryVisibility.WorldReadable, + isEncrypted = true, ) ) // Saved settings are updated 3 times to match the edited settings diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt index ebded7fbe97..d28dd00c35a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt @@ -9,8 +9,9 @@ package io.element.android.libraries.matrix.api.analytics import im.vector.app.features.analytics.plan.ViewRoom import io.element.android.libraries.matrix.api.room.MatrixRoom +import kotlinx.coroutines.flow.first -fun MatrixRoom.toAnalyticsViewRoom( +suspend fun MatrixRoom.toAnalyticsViewRoom( trigger: ViewRoom.Trigger? = null, selectedSpace: MatrixRoom? = null, viaKeyboard: Boolean? = null, @@ -18,7 +19,7 @@ fun MatrixRoom.toAnalyticsViewRoom( val activeSpace = selectedSpace?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home return ViewRoom( - isDM = isDirect, + isDM = roomInfoFlow.first().isDirect, isSpace = isSpace, trigger = trigger, activeSpace = activeSpace, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt index 8d1c730bb3b..5dad2a0da6a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt @@ -27,6 +27,7 @@ data class MatrixRoomInfo( val rawName: String?, val topic: String?, val avatarUrl: String?, + val isPublic: Boolean, val isDirect: Boolean, val isEncrypted: Boolean?, val joinRule: JoinRule?, @@ -76,4 +77,42 @@ data class MatrixRoomInfo( ) { val aliases: List get() = listOfNotNull(canonicalAlias) + alternativeAliases + + companion object { + fun fromRoom(matrixRoom: MatrixRoom) = MatrixRoomInfo( + id = matrixRoom.roomId, + name = matrixRoom.displayName, + rawName = matrixRoom.displayName, + topic = matrixRoom.topic, + avatarUrl = matrixRoom.avatarUrl, + isPublic = matrixRoom.isPublic, + isDirect = matrixRoom.isDirect, + isEncrypted = matrixRoom.isEncrypted, + joinRule = null, + isSpace = matrixRoom.isSpace, + isTombstoned = false, + isFavorite = false, + canonicalAlias = TODO(), + alternativeAliases = TODO(), + currentUserMembership = TODO(), + inviter = TODO(), + activeMembersCount = TODO(), + invitedMembersCount = TODO(), + joinedMembersCount = TODO(), + userPowerLevels = TODO(), + highlightCount = TODO(), + notificationCount = TODO(), + userDefinedNotificationMode = TODO(), + hasRoomCall = TODO(), + activeRoomCallParticipants = TODO(), + isMarkedUnread = TODO(), + numUnreadMessages = TODO(), + numUnreadNotifications = TODO(), + numUnreadMentions = TODO(), + heroes = TODO(), + pinnedEventIds = TODO(), + creator = TODO(), + historyVisibility = TODO() + ) + } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt index b1c9787bfa2..3dd54db9b1a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomIsDmCheck.kt @@ -7,6 +7,8 @@ package io.element.android.libraries.matrix.api.room +import kotlinx.coroutines.flow.first + /** * Returns whether the room with the provided info is a DM. * A DM is a room with at most 2 active members (one of them may have left). @@ -19,9 +21,9 @@ fun isDm(isDirect: Boolean, activeMembersCount: Int): Boolean { } /** - * Returns whether the [MatrixRoom] is a DM. + * Returns whether the [MatrixRoom] is a DM, with an updated state from the latest [MatrixRoomInfo]. */ -val MatrixRoom.isDm get() = isDm(isDirect, activeMemberCount.toInt()) +suspend fun MatrixRoom.isDm() = roomInfoFlow.first().isDm /** * Returns whether the [MatrixRoomInfo] is from a DM. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt index 3e1d77fd3cf..973b5cca0d2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/recent/RecentDirectRoom.kt @@ -32,7 +32,7 @@ suspend fun MatrixClient.getRecentDirectRooms( getRecentlyVisitedRooms().getOrNull()?.let { roomIds -> roomIds .mapNotNull { roomId -> getRoom(roomId) } - .filter { it.isDm && it.isJoined() } + .filter { it.isDm() && it.isJoined() } .map { room -> val otherUser = room.getMembers().getOrNull() ?.firstOrNull { it.userId != sessionId } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index cbcd67296ac..45d6188f0f6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -241,16 +241,6 @@ class RustMatrixClient( // Force a refresh of the profile getUserProfile() - - // And emit a new value - _userProfile.emit( - MatrixUser( - userId = sessionId, - // TODO cache for displayName? - displayName = null, - avatarUrl = innerClient.cachedAvatarUrl(), - ) - ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExt.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExt.kt index f98f9fedc82..1e76e69fa47 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExt.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExt.kt @@ -9,6 +9,8 @@ package io.element.android.libraries.matrix.impl.analytics import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.isDm +import kotlinx.coroutines.flow.first private fun Long.toAnalyticsRoomSize(): JoinedRoom.RoomSize { return when (this) { @@ -22,11 +24,12 @@ private fun Long.toAnalyticsRoomSize(): JoinedRoom.RoomSize { } } -fun MatrixRoom.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom { +suspend fun MatrixRoom.toAnalyticsJoinedRoom(trigger: JoinedRoom.Trigger?): JoinedRoom { + val roomInfo = roomInfoFlow.first() return JoinedRoom( - isDM = isDirect, - isSpace = isSpace, - roomSize = joinedMemberCount.toAnalyticsRoomSize(), + isDM = roomInfo.isDm, + isSpace = roomInfo.isSpace, + roomSize = roomInfo.joinedMembersCount.toAnalyticsRoomSize(), trigger = trigger ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt index b6343644bcf..ba74c16a264 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt @@ -37,6 +37,7 @@ class MatrixRoomInfoMapper { rawName = it.rawName, topic = it.topic, avatarUrl = it.avatarUrl, + isPublic = it.isPublic, isDirect = it.isDirect, isEncrypted = when (it.encryptionState) { EncryptionState.ENCRYPTED -> true diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 5a6b0671774..4bb4b75faba 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -73,6 +73,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine @@ -82,6 +83,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode @@ -141,7 +143,7 @@ class RustMatrixRoom( channel.trySend(matrixRoomInfoMapper.map(roomInfo)) } }) - } + }.shareIn(sessionCoroutineScope, started = SharingStarted.Lazily, replay = 1) override val roomTypingMembersFlow: Flow> = mxCallbackFlow { val initial = emptyList() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index 3bd3699037a..46c2bfebd48 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -56,6 +56,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.launchIn @@ -214,19 +215,19 @@ class RustTimeline( _timelineItems, backPaginationStatus, forwardPaginationStatus, - matrixRoom.roomInfoFlow.map { it.creator }, + matrixRoom.roomInfoFlow.map { it.creator to it.isDm }.distinctUntilChanged(), isTimelineInitialized, ) { timelineItems, backwardPaginationStatus, forwardPaginationStatus, - roomCreator, + (roomCreator, isDm), isTimelineInitialized -> withContext(dispatcher) { timelineItems .let { items -> roomBeginningPostProcessor.process( items = items, - isDm = matrixRoom.isDm, + isDm = isDm, roomCreator = roomCreator, hasMoreToLoadBackwards = backwardPaginationStatus.hasMoreToLoad, ) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt index c6fc62348e2..887155e5d70 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactoryTest.kt @@ -42,7 +42,7 @@ fun TestScope.createRustMatrixClientFactory( ) = RustMatrixClientFactory( baseDirectory = baseDirectory, cacheDirectory = cacheDirectory, - appCoroutineScope = this, + appCoroutineScope = backgroundScope, coroutineDispatchers = testCoroutineDispatchers(), sessionStore = sessionStore, userAgentProvider = SimpleUserAgentProvider(), diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt index 0af84fc6e3b..43bae9a208e 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientTest.kt @@ -38,7 +38,7 @@ class RustMatrixClientTest { innerClient = FakeRustClient(), baseDirectory = File(""), sessionStore = sessionStore, - appCoroutineScope = this, + appCoroutineScope = backgroundScope, sessionDelegate = aRustClientSessionDelegate( sessionStore = sessionStore, ), diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExtKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExtKtTest.kt index bce9097c5f4..16880a7769c 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExtKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExtKtTest.kt @@ -11,11 +11,13 @@ import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo +import kotlinx.coroutines.test.runTest import org.junit.Test class JoinedRoomExtKtTest { @Test - fun `test room size mapping`() { + fun `test room size mapping`() = runTest { mapOf( listOf(0L, 1L) to JoinedRoom.RoomSize.One, listOf(2L, 2L) to JoinedRoom.RoomSize.Two, @@ -25,7 +27,10 @@ class JoinedRoomExtKtTest { listOf(1001L, 2000L) to JoinedRoom.RoomSize.MoreThanAThousand ).forEach { (joinedMemberCounts, expectedRoomSize) -> joinedMemberCounts.forEach { joinedMemberCount -> - assertThat(aMatrixRoom(joinedMemberCount = joinedMemberCount).toAnalyticsJoinedRoom(null)) + val room = aMatrixRoom().apply { + givenRoomInfo(aRoomInfo(joinedMembersCount = joinedMemberCount)) + } + assertThat(room.toAnalyticsJoinedRoom(null)) .isEqualTo( JoinedRoom( isDM = false, @@ -39,8 +44,9 @@ class JoinedRoomExtKtTest { } @Test - fun `test isDirect parameter mapping`() { - assertThat(aMatrixRoom(isDirect = true).toAnalyticsJoinedRoom(null)) + fun `test isDirect parameter mapping`() = runTest { + val room = aMatrixRoom().apply { givenRoomInfo(aRoomInfo(isDirect = true)) } + assertThat(room.toAnalyticsJoinedRoom(null)) .isEqualTo( JoinedRoom( isDM = true, @@ -52,8 +58,9 @@ class JoinedRoomExtKtTest { } @Test - fun `test isSpace parameter mapping`() { - assertThat(aMatrixRoom(isSpace = true).toAnalyticsJoinedRoom(null)) + fun `test isSpace parameter mapping`() = runTest { + val room = aMatrixRoom().apply { givenRoomInfo(aRoomInfo(isSpace = true)) } + assertThat(room.toAnalyticsJoinedRoom(null)) .isEqualTo( JoinedRoom( isDM = false, @@ -65,8 +72,9 @@ class JoinedRoomExtKtTest { } @Test - fun `test trigger parameter mapping`() { - assertThat(aMatrixRoom().toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite)) + fun `test trigger parameter mapping`() = runTest { + val room = aMatrixRoom().apply { givenRoomInfo(aRoomInfo(isDirect = false, isSpace = false)) } + assertThat(room.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite)) .isEqualTo( JoinedRoom( isDM = false, @@ -81,11 +89,11 @@ class JoinedRoomExtKtTest { isDirect: Boolean = false, isSpace: Boolean = false, joinedMemberCount: Long = 0 - ): MatrixRoom { + ): FakeMatrixRoom { return FakeMatrixRoom( isDirect = isDirect, isSpace = isSpace, - joinedMemberCount = joinedMemberCount + joinedMemberCount = joinedMemberCount, ) } } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomInfo.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomInfo.kt index f5341a1c5e8..15445183c0d 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomInfo.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomInfo.kt @@ -17,6 +17,7 @@ import org.matrix.rustcomponents.sdk.RoomHistoryVisibility import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomMember import org.matrix.rustcomponents.sdk.RoomNotificationMode +import uniffi.matrix_sdk_base.EncryptionState fun aRustRoomInfo( id: String = A_ROOM_ID.value, @@ -24,6 +25,7 @@ fun aRustRoomInfo( rawName: String? = A_ROOM_NAME, topic: String? = null, avatarUrl: String? = null, + encryptionState: EncryptionState = EncryptionState.UNKNOWN, isDirect: Boolean = false, isPublic: Boolean = false, isSpace: Boolean = false, @@ -57,6 +59,7 @@ fun aRustRoomInfo( rawName = rawName, topic = topic, avatarUrl = avatarUrl, + encryptionState = encryptionState, isDirect = isDirect, isPublic = isPublic, isSpace = isSpace, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClient.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClient.kt index 0a9983d60f7..55dd385b6a1 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClient.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRustClient.kt @@ -35,11 +35,11 @@ class FakeRustClient( override fun userId(): String = userId override fun deviceId(): String = deviceId override suspend fun notificationClient(processSetup: NotificationProcessSetup) = notificationClient - override fun getNotificationSettings(): NotificationSettings = notificationSettings + override suspend fun getNotificationSettings(): NotificationSettings = notificationSettings override fun encryption(): Encryption = encryption override fun session(): Session = session override fun setDelegate(delegate: ClientDelegate?): TaskHandle = FakeRustTaskHandle() - override fun cachedAvatarUrl(): String? = null + override suspend fun cachedAvatarUrl(): String? = null override suspend fun restoreSession(session: Session) = Unit override fun syncService(): SyncServiceBuilder = FakeRustSyncServiceBuilder() override fun roomDirectorySearch(): RoomDirectorySearch = FakeRustRoomDirectorySearch() diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsServiceTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsServiceTest.kt index 83461b97506..34b8c114b62 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsServiceTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/notificationsettings/RustNotificationSettingsServiceTest.kt @@ -37,6 +37,7 @@ class RustNotificationSettingsServiceTest { client = FakeRustClient( notificationSettings = notificationSettings, ), + sessionCoroutineScope = this, dispatchers = testCoroutineDispatchers(), ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt index 1befd53ae0e..33ce5c8c333 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapperTest.kt @@ -33,6 +33,7 @@ import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toPersistentList import org.junit.Test import org.matrix.rustcomponents.sdk.Membership +import uniffi.matrix_sdk_base.EncryptionState import org.matrix.rustcomponents.sdk.JoinRule as RustJoinRule import org.matrix.rustcomponents.sdk.RoomHistoryVisibility as RustRoomHistoryVisibility import org.matrix.rustcomponents.sdk.RoomNotificationMode as RustRoomNotificationMode @@ -48,6 +49,7 @@ class MatrixRoomInfoMapperTest { rawName = "rawName", topic = "topic", avatarUrl = AN_AVATAR_URL, + encryptionState = EncryptionState.ENCRYPTED, isDirect = true, isPublic = false, isSpace = false, @@ -84,7 +86,9 @@ class MatrixRoomInfoMapperTest { rawName = "rawName", topic = "topic", avatarUrl = AN_AVATAR_URL, + isPublic = false, isDirect = true, + isEncrypted = true, isSpace = false, isTombstoned = false, isFavorite = false, @@ -130,6 +134,7 @@ class MatrixRoomInfoMapperTest { rawName = null, topic = null, avatarUrl = null, + encryptionState = EncryptionState.UNKNOWN, isDirect = false, isPublic = true, joinRule = null, @@ -165,6 +170,8 @@ class MatrixRoomInfoMapperTest { rawName = null, topic = null, avatarUrl = null, + isEncrypted = null, + isPublic = true, isDirect = false, joinRule = null, isSpace = false, diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt index ede0b4b728e..9806cbb1cb9 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/room/join/DefaultJoinRoomTest.kt @@ -19,6 +19,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_SERVER_LIST import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -32,7 +33,9 @@ class DefaultJoinRoomTest { val roomSummary = aRoomSummary() val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) } val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List -> Result.success(roomSummary) } - val roomResult = FakeMatrixRoom() + val roomResult = FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo()) + } val aTrigger = JoinedRoom.Trigger.MobilePermalink val client: MatrixClient = FakeMatrixClient().also { it.joinRoomLambda = joinRoomLambda @@ -67,7 +70,9 @@ class DefaultJoinRoomTest { val roomSummary = aRoomSummary() val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) } val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List -> Result.success(roomSummary) } - val roomResult = FakeMatrixRoom() + val roomResult = FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo()) + } val aTrigger = JoinedRoom.Trigger.MobilePermalink val client: MatrixClient = FakeMatrixClient().also { it.joinRoomLambda = joinRoomLambda @@ -103,7 +108,9 @@ class DefaultJoinRoomTest { val roomSummary = aRoomSummary() val joinRoomLambda = lambdaRecorder { _: RoomId -> Result.success(roomSummary) } val joinRoomByIdOrAliasLambda = lambdaRecorder { _: RoomIdOrAlias, _: List -> Result.success(roomSummary) } - val roomResult = FakeMatrixRoom() + val roomResult = FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo()) + } val aTrigger = JoinedRoom.Trigger.MobilePermalink val client: MatrixClient = FakeMatrixClient().also { it.joinRoomLambda = joinRoomLambda diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt index 50a4c30a853..cc56c2d32fb 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomInfoFixture.kt @@ -33,7 +33,9 @@ fun aRoomInfo( rawName: String? = A_ROOM_RAW_NAME, topic: String? = A_ROOM_TOPIC, avatarUrl: String? = AN_AVATAR_URL, + isPublic: Boolean = true, isDirect: Boolean = false, + isEncrypted: Boolean = false, joinRule: JoinRule? = JoinRule.Public, isSpace: Boolean = false, isTombstoned: Boolean = false, @@ -65,7 +67,9 @@ fun aRoomInfo( rawName = rawName, topic = topic, avatarUrl = avatarUrl, + isPublic = isPublic, isDirect = isDirect, + isEncrypted = isEncrypted, joinRule = joinRule, isSpace = isSpace, isTombstoned = isTombstoned, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index c85fe824d5f..ac561c57d25 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -46,7 +46,9 @@ fun aRoomSummary( rawName: String? = A_ROOM_RAW_NAME, topic: String? = A_ROOM_TOPIC, avatarUrl: String? = null, + isPublic: Boolean = true, isDirect: Boolean = false, + isEncrypted: Boolean = false, joinRule: JoinRule? = JoinRule.Public, isSpace: Boolean = false, isTombstoned: Boolean = false, @@ -80,7 +82,9 @@ fun aRoomSummary( rawName = rawName, topic = topic, avatarUrl = avatarUrl, + isPublic = isPublic, isDirect = isDirect, + isEncrypted = isEncrypted, joinRule = joinRule, isSpace = isSpace, isTombstoned = isTombstoned, diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt index 8ab1a4b9f50..899f2c802d5 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt @@ -40,11 +40,12 @@ fun getRoomMemberAsState(roomMembersState: MatrixRoomMembersState, userId: UserI @Composable fun MatrixRoom.getDirectRoomMember(roomMembersState: MatrixRoomMembersState): State { val roomMembers = roomMembersState.roomMembers() - return remember(roomMembersState) { + val roomInfo by roomInfoFlow.collectAsState(null) + return remember(roomMembersState, roomInfo?.isDirect) { derivedStateOf { roomMembers ?.filter { it.membership.isActive() } - ?.takeIf { it.size == 2 && isDirect } + ?.takeIf { it.size == 2 && roomInfo?.isDirect == true } ?.find { it.userId != sessionId } } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt index f7f47b78945..d6b3406c7a9 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt @@ -67,9 +67,9 @@ fun MatrixRoom.canPinUnpin(updateKey: Long): State { } @Composable -fun MatrixRoom.isDmAsState(updateKey: Long): State { - return produceState(initialValue = false, key1 = updateKey) { - value = isDm +fun MatrixRoom.isDmAsState(): State { + return produceState(initialValue = false) { + roomInfoFlow.collect { value = it.isDm } } } diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt index 4187fe6e904..9a0d766b92b 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt @@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_3 import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest @@ -31,14 +32,20 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emits other member for encrypted DM with 2 joined members`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - isEncrypted = true, - isDirect = true, - ) + ).apply { + givenRoomInfo(aRoomInfo( + isDirect = true, + joinedMembersCount = 2, + )) + } moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready(persistentListOf(roomMember1, roomMember2)) ) }.test { + // Skip initial item with outdated info + skipItems(1) + // Check the updated info assertThat(awaitItem().value).isEqualTo(roomMember2) } } @@ -64,13 +71,20 @@ class MatrixRoomMembersTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, isEncrypted = false, - isDirect = true, - ) + ).apply { + givenRoomInfo(aRoomInfo( + isDirect = true, + activeMembersCount = 2, + )) + } moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready(persistentListOf(roomMember1, roomMember2)) ) }.test { + // Skip initial item with outdated info + skipItems(1) + // Check the updated info assertThat(awaitItem().value).isEqualTo(roomMember2) } } @@ -132,9 +146,12 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit the other member if there are 2 active members`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - isEncrypted = true, - isDirect = true, - ) + ).apply { + givenRoomInfo(aRoomInfo( + isDirect = true, + activeMembersCount = 2, + )) + } moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready( @@ -146,6 +163,9 @@ class MatrixRoomMembersTest { ) ) }.test { + // Skip initial item with outdated info + skipItems(1) + // Check the updated info assertThat(awaitItem().value).isEqualTo(roomMember2) } } diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt index 070f8769733..bf98fdda087 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt @@ -153,7 +153,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( imageMimeType = null, threadId = threadId, roomName = room.displayName, - roomIsDm = room.isDm, + roomIsDm = room.isDm(), outGoingMessage = true, ) onNotifiableEventReceived.onNotifiableEventReceived(notifiableMessageEvent) diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt index 17093fef43c..0ce6038d798 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.preferences.api.store.SessionPreferencesStore @@ -337,7 +338,14 @@ class NotificationBroadcastReceiverHandlerTest { val matrixRoom = FakeMatrixRoom( liveTimeline = liveTimeline, getUpdatedMemberResult = { Result.success(aRoomMember()) }, - ) + ).apply { + givenRoomInfo( + aRoomInfo( + isDirect = true, + activeMembersCount = 2, + ) + ) + } val onNotifiableEventReceivedResult = lambdaRecorder { _ -> } val onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceivedResult = onNotifiableEventReceivedResult) val sut = createNotificationBroadcastReceiverHandler( @@ -396,7 +404,14 @@ class NotificationBroadcastReceiverHandlerTest { val matrixRoom = FakeMatrixRoom( liveTimeline = liveTimeline, getUpdatedMemberResult = { Result.success(aRoomMember()) }, - ) + ).apply { + givenRoomInfo( + aRoomInfo( + isDirect = true, + activeMembersCount = 2, + ) + ) + } val onNotifiableEventReceivedResult = lambdaRecorder { _ -> } val onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceivedResult = onNotifiableEventReceivedResult) val sut = createNotificationBroadcastReceiverHandler( From 65fcf5e8e07c659718ac0d489315356c903bdd91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 13 Mar 2025 15:56:20 +0100 Subject: [PATCH 05/10] Remove `MatrixRoom.isDirect`, `RoomInfo.isDirect` should be used instead --- .../messages/impl/MessagesPresenterTest.kt | 2 - .../MessageComposerPresenterTest.kt | 1 - .../RoomNotificationSettingsPresenter.kt | 4 +- .../roomdetails/impl/MatrixRoomFixture.kt | 3 +- .../RoomMembersModerationPresenterTest.kt | 3 -- .../libraries/matrix/api/room/MatrixRoom.kt | 1 - .../matrix/api/room/MatrixRoomInfo.kt | 38 ------------------- .../matrix/impl/room/RustMatrixRoom.kt | 4 -- .../impl/analytics/JoinedRoomExtKtTest.kt | 23 ++++------- .../matrix/test/room/FakeMatrixRoom.kt | 1 - .../matrix/ui/room/MatrixRoomMembersTest.kt | 24 ++++++------ 11 files changed, 23 insertions(+), 81 deletions(-) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 03905c6860c..38f3fa8cdb4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -95,7 +95,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -594,7 +593,6 @@ class MessagesPresenterTest { fun `present - doesn't show reinvite prompt if other party is present`() = runTest { val room = FakeMatrixRoom( sessionId = A_SESSION_ID, - isDirect = true, activeMemberCount = 2L, canUserSendMessageResult = { _, _ -> Result.success(true) }, canRedactOwnResult = { Result.success(true) }, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index 6f7bcd31e58..e4781da70ef 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -1061,7 +1061,6 @@ class MessageComposerPresenterTest { val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN) val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) val room = FakeMatrixRoom( - isDirect = true, activeMemberCount = 2, isEncrypted = true, canUserTriggerRoomNotificationResult = { Result.success(true) }, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index 8f3dae9f3d4..cb86da0233a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -90,8 +90,8 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( } LaunchedEffect(isRoomEncrypted) { - shouldDisplayMentionsOnlyDisclaimer = isRoomEncrypted == true - && !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) + shouldDisplayMentionsOnlyDisclaimer = isRoomEncrypted == true && + !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) } fun handleEvents(event: RoomNotificationSettingsEvents) { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt index d9cc77be76c..0c418c0721b 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt @@ -51,7 +51,6 @@ fun aMatrixRoom( avatarUrl = avatarUrl, isEncrypted = isEncrypted, isPublic = isPublic, - isDirect = isDirect, notificationSettingsService = notificationSettingsService, canInviteResult = canInviteResult, canBanResult = canBanResult, @@ -73,6 +72,8 @@ fun aMatrixRoom( topic = topic, avatarUrl = avatarUrl, isDirect = isDirect, + isPublic = isPublic, + isEncrypted = isEncrypted, joinRule = joinRule, ) ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt index d569a93baa5..ca54890d271 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt @@ -35,7 +35,6 @@ class RoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when room is DM is false`() = runTest { val room = FakeMatrixRoom( - isDirect = true, isPublic = true, activeMemberCount = 2, canKickResult = { Result.success(true) }, @@ -53,7 +52,6 @@ class RoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when user can kick other users, FF is enabled and room is not a DM returns true`() = runTest { val room = FakeMatrixRoom( - isDirect = false, activeMemberCount = 10, canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, @@ -69,7 +67,6 @@ class RoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when user can ban other users, FF is enabled and room is not a DM returns true`() = runTest { val room = FakeMatrixRoom( - isDirect = false, activeMemberCount = 10, canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 3f85f8f247c..e75c90765c5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -52,7 +52,6 @@ interface MatrixRoom : Closeable { val avatarUrl: String? val isEncrypted: Boolean? val isSpace: Boolean - val isDirect: Boolean val isPublic: Boolean val activeMemberCount: Long val joinedMemberCount: Long diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt index 5dad2a0da6a..ad526c6b404 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt @@ -77,42 +77,4 @@ data class MatrixRoomInfo( ) { val aliases: List get() = listOfNotNull(canonicalAlias) + alternativeAliases - - companion object { - fun fromRoom(matrixRoom: MatrixRoom) = MatrixRoomInfo( - id = matrixRoom.roomId, - name = matrixRoom.displayName, - rawName = matrixRoom.displayName, - topic = matrixRoom.topic, - avatarUrl = matrixRoom.avatarUrl, - isPublic = matrixRoom.isPublic, - isDirect = matrixRoom.isDirect, - isEncrypted = matrixRoom.isEncrypted, - joinRule = null, - isSpace = matrixRoom.isSpace, - isTombstoned = false, - isFavorite = false, - canonicalAlias = TODO(), - alternativeAliases = TODO(), - currentUserMembership = TODO(), - inviter = TODO(), - activeMembersCount = TODO(), - invitedMembersCount = TODO(), - joinedMembersCount = TODO(), - userPowerLevels = TODO(), - highlightCount = TODO(), - notificationCount = TODO(), - userDefinedNotificationMode = TODO(), - hasRoomCall = TODO(), - activeRoomCallParticipants = TODO(), - isMarkedUnread = TODO(), - numUnreadMessages = TODO(), - numUnreadNotifications = TODO(), - numUnreadMentions = TODO(), - heroes = TODO(), - pinnedEventIds = TODO(), - creator = TODO(), - historyVisibility = TODO() - ) - } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 4bb4b75faba..e68357041f3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -84,7 +84,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.shareIn -import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener @@ -341,9 +340,6 @@ class RustMatrixRoom( override val isSpace: Boolean get() = runCatching { innerRoom.isSpace() }.getOrDefault(false) - override val isDirect: Boolean - get() = runCatching { runBlocking { innerRoom.isDirect() } }.getOrDefault(false) - override val joinedMemberCount: Long get() = runCatching { innerRoom.joinedMembersCount().toLong() }.getOrDefault(0) diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExtKtTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExtKtTest.kt index 16880a7769c..1ecfb672b81 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExtKtTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/analytics/JoinedRoomExtKtTest.kt @@ -9,7 +9,6 @@ package io.element.android.libraries.matrix.impl.analytics import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.JoinedRoom -import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import kotlinx.coroutines.test.runTest @@ -27,10 +26,7 @@ class JoinedRoomExtKtTest { listOf(1001L, 2000L) to JoinedRoom.RoomSize.MoreThanAThousand ).forEach { (joinedMemberCounts, expectedRoomSize) -> joinedMemberCounts.forEach { joinedMemberCount -> - val room = aMatrixRoom().apply { - givenRoomInfo(aRoomInfo(joinedMembersCount = joinedMemberCount)) - } - assertThat(room.toAnalyticsJoinedRoom(null)) + assertThat(aMatrixRoom(joinedMemberCount = joinedMemberCount).toAnalyticsJoinedRoom(null)) .isEqualTo( JoinedRoom( isDM = false, @@ -45,8 +41,7 @@ class JoinedRoomExtKtTest { @Test fun `test isDirect parameter mapping`() = runTest { - val room = aMatrixRoom().apply { givenRoomInfo(aRoomInfo(isDirect = true)) } - assertThat(room.toAnalyticsJoinedRoom(null)) + assertThat(aMatrixRoom(isDirect = true).toAnalyticsJoinedRoom(null)) .isEqualTo( JoinedRoom( isDM = true, @@ -59,8 +54,7 @@ class JoinedRoomExtKtTest { @Test fun `test isSpace parameter mapping`() = runTest { - val room = aMatrixRoom().apply { givenRoomInfo(aRoomInfo(isSpace = true)) } - assertThat(room.toAnalyticsJoinedRoom(null)) + assertThat(aMatrixRoom(isSpace = true).toAnalyticsJoinedRoom(null)) .isEqualTo( JoinedRoom( isDM = false, @@ -73,8 +67,7 @@ class JoinedRoomExtKtTest { @Test fun `test trigger parameter mapping`() = runTest { - val room = aMatrixRoom().apply { givenRoomInfo(aRoomInfo(isDirect = false, isSpace = false)) } - assertThat(room.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite)) + assertThat(aMatrixRoom(isDirect = false, isSpace = false, joinedMemberCount = 1).toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite)) .isEqualTo( JoinedRoom( isDM = false, @@ -90,10 +83,8 @@ class JoinedRoomExtKtTest { isSpace: Boolean = false, joinedMemberCount: Long = 0 ): FakeMatrixRoom { - return FakeMatrixRoom( - isDirect = isDirect, - isSpace = isSpace, - joinedMemberCount = joinedMemberCount, - ) + return FakeMatrixRoom().apply { + givenRoomInfo(aRoomInfo(isDirect = isDirect, isSpace = isSpace, joinedMembersCount = joinedMemberCount)) + } } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 522346fbab2..5892695948c 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -74,7 +74,6 @@ class FakeMatrixRoom( override val alternativeAliases: List = emptyList(), override val isPublic: Boolean = true, override val isSpace: Boolean = false, - override val isDirect: Boolean = false, override val joinedMemberCount: Long = 123L, override val activeMemberCount: Long = 234L, val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt index 9a0d766b92b..2bc2fee9780 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt @@ -54,8 +54,6 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit null if the room is not a dm`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - isEncrypted = true, - isDirect = false, ) moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( @@ -70,7 +68,6 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emits other member even if the room is not encrypted`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - isEncrypted = false, ).apply { givenRoomInfo(aRoomInfo( isDirect = true, @@ -93,14 +90,15 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit null if the room has only 1 member`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - isEncrypted = true, - isDirect = true, - ) + ).apply { + givenRoomInfo(aRoomInfo(isDirect = true)) + } moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready(persistentListOf(roomMember1)) ) }.test { + skipItems(1) assertThat(awaitItem().value).isNull() } } @@ -109,14 +107,15 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit null if the room has only 3 members`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - isEncrypted = true, - isDirect = true, - ) + ).apply { + givenRoomInfo(aRoomInfo(isDirect = true)) + } moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready(persistentListOf(roomMember1, roomMember2, roomMember3)) ) }.test { + skipItems(1) assertThat(awaitItem().value).isNull() } } @@ -125,9 +124,9 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit null if the other member is not active`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - isEncrypted = true, - isDirect = true, - ) + ).apply { + givenRoomInfo(aRoomInfo(isDirect = true)) + } moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready( @@ -138,6 +137,7 @@ class MatrixRoomMembersTest { ) ) }.test { + skipItems(1) assertThat(awaitItem().value).isNull() } } From 4bfec7e31764869ca8c3b8f7b3d3c8839a9b8b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 13 Mar 2025 16:50:45 +0100 Subject: [PATCH 06/10] Upgrade SDK to `v25.03.13` --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ff83f06bba..f7d72b68a38 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -174,7 +174,7 @@ jsoup = "org.jsoup:jsoup:1.19.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.3.11" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.3.13" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From 6296fb3a4f38fa30cac4cd87bee2b79dbdd41ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 13 Mar 2025 16:51:07 +0100 Subject: [PATCH 07/10] Initial migration to using always `RoomInfo` for storing the updated room info --- .../impl/utils/DefaultCallWidgetProvider.kt | 2 +- .../messages/impl/MessagesPresenter.kt | 2 +- .../list/PinnedMessagesListPresenter.kt | 2 +- .../impl/timeline/TimelinePresenter.kt | 2 +- .../roomdetails/impl/RoomDetailsPresenter.kt | 22 ++++---- .../RoomNotificationSettingsPresenter.kt | 12 +++-- .../EditRoomAddressPresenter.kt | 19 ++++--- .../matrix/api/analytics/ViewRoomExt.kt | 9 ++-- .../libraries/matrix/api/room/MatrixRoom.kt | 16 ++---- .../matrix/api/room/alias/MatrixRoomAlias.kt | 2 +- .../matrix/impl/room/MatrixRoomInfoMapper.kt | 38 +++++++++++++ .../matrix/impl/room/RustMatrixRoom.kt | 53 ++----------------- .../matrix/impl/room/RustRoomFactory.kt | 3 +- .../impl/gallery/MediaGalleryPresenter.kt | 2 +- .../NotificationBroadcastReceiverHandler.kt | 2 +- 15 files changed, 92 insertions(+), 94 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt index 70c1916a575..4f2ae0693f1 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt @@ -38,7 +38,7 @@ class DefaultCallWidgetProvider @Inject constructor( val baseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull() ?: elementCallBaseUrlProvider.provides(matrixClient) ?: ElementCallConfig.DEFAULT_BASE_URL - val isEncrypted = room.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() + val isEncrypted = room.info.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = isEncrypted) val callUrl = room.generateWidgetWebViewUrl( widgetSettings = widgetSettings, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 6a6e144afcf..9a4b7b913ee 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -276,7 +276,7 @@ class MessagesPresenter @AssistedInject constructor( return AvatarData( id = id.value, name = name, - url = avatarUrl ?: room.avatarUrl, + url = avatarUrl ?: room.info.avatarUrl, size = AvatarSize.TimelineRoom ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt index 947563d4cf3..322c283f003 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -90,7 +90,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor( val timelineRoomInfo = remember(isDm) { TimelineRoomInfo( isDm = isDm, - name = room.displayName, + name = room.info.name, // We don't need to compute those values userHasPermissionToSendMessage = false, userHasPermissionToSendReaction = false, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index f522ee48415..c1fefc46854 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -235,7 +235,7 @@ class TimelinePresenter @AssistedInject constructor( val timelineRoomInfo by remember(typingNotificationState, roomCallState, roomInfo) { derivedStateOf { TimelineRoomInfo( - name = roomInfo?.name ?: room.displayName, + name = roomInfo?.name, isDm = roomInfo?.isDm.orFalse(), userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendReaction = userHasPermissionToSendReaction, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 14bf391c44d..fc6b6e4ce4c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -73,22 +73,22 @@ class RoomDetailsPresenter @Inject constructor( val scope = rememberCoroutineScope() val leaveRoomState = leaveRoomPresenter.present() val canShowNotificationSettings = remember { mutableStateOf(false) } - val roomInfo by room.roomInfoFlow.collectAsState(initial = null) + val roomInfo by room.roomInfoFlow.collectAsState() val isUserAdmin = room.isOwnUserAdmin() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val roomAvatar by remember { derivedStateOf { roomInfo?.avatarUrl ?: room.avatarUrl } } + val roomAvatar by remember { derivedStateOf { roomInfo.avatarUrl } } - val roomName by remember { derivedStateOf { (roomInfo?.name ?: room.displayName).trim() } } - val roomTopic by remember { derivedStateOf { roomInfo?.topic ?: room.topic } } - val isFavorite by remember { derivedStateOf { roomInfo?.isFavorite.orFalse() } } - val joinRule by remember { derivedStateOf { roomInfo?.joinRule } } + val roomName by remember { derivedStateOf { roomInfo.name?.trim().orEmpty() } } + val roomTopic by remember { derivedStateOf { roomInfo.topic } } + val isFavorite by remember { derivedStateOf { roomInfo.isFavorite } } + val joinRule by remember { derivedStateOf { roomInfo.joinRule } } val canShowPinnedMessages = isPinnedMessagesFeatureEnabled() var canShowMediaGallery by remember { mutableStateOf(false) } LaunchedEffect(Unit) { canShowMediaGallery = featureFlagService.isFeatureEnabled(FeatureFlags.MediaGallery) } - val pinnedMessagesCount by remember { derivedStateOf { roomInfo?.pinnedEventIds?.size } } + val pinnedMessagesCount by remember { derivedStateOf { roomInfo.pinnedEventIds.size } } LaunchedEffect(Unit) { canShowNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings) @@ -101,7 +101,8 @@ class RoomDetailsPresenter @Inject constructor( val membersState by room.membersStateFlow.collectAsState() val canInvite by getCanInvite(membersState) - val isEncrypted by remember { derivedStateOf { roomInfo?.isEncrypted == true } } + val canonicalAlias by remember { derivedStateOf { roomInfo.canonicalAlias }} + val isEncrypted by remember { derivedStateOf { roomInfo.isEncrypted == true } } val isDm by room.isDmAsState() val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME) val canEditAvatar by getCanSendState(membersState, StateEventType.ROOM_AVATAR) @@ -111,6 +112,7 @@ class RoomDetailsPresenter @Inject constructor( val roomMemberDetailsPresenter = roomMemberDetailsPresenter(dmMember) val roomType = getRoomType(dmMember, currentMember) val roomCallState = roomCallStatePresenter.present() + val joinedMemberCount by remember { derivedStateOf { roomInfo.joinedMembersCount } } val topicState = remember(canEditTopic, roomTopic, roomType) { val topic = roomTopic @@ -168,10 +170,10 @@ class RoomDetailsPresenter @Inject constructor( return RoomDetailsState( roomId = room.roomId, roomName = roomName, - roomAlias = room.canonicalAlias, + roomAlias = canonicalAlias, roomAvatarUrl = roomAvatar, roomTopic = topicState, - memberCount = room.joinedMemberCount, + memberCount = joinedMemberCount, isEncrypted = isEncrypted, canInvite = canInvite, canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index cb86da0233a..8f5e24dfc95 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -79,7 +79,11 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( mutableStateOf(null) } - val isRoomEncrypted by produceState(room.isEncrypted) { + val displayName by produceState(room.info.name) { + room.roomInfoFlow.collect { value = it.name } + } + + val isRoomEncrypted by produceState(room.info.isEncrypted) { room.roomInfoFlow.collect { value = it.isEncrypted } } @@ -122,7 +126,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( return RoomNotificationSettingsState( showUserDefinedSettingStyle = showUserDefinedSettingStyle, - roomName = room.displayName, + roomName = displayName.orEmpty(), roomNotificationSettings = roomNotificationSettings.value, pendingRoomNotificationMode = pendingRoomNotificationMode.value, pendingSetDefault = pendingSetDefault.value, @@ -152,7 +156,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( roomNotificationSettings: MutableState> ) = launch { suspend { - val isEncrypted = room.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() + val isEncrypted = room.info.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() pendingModeState.value = null notificationSettingsService.getRoomNotificationSettings(room.roomId, isEncrypted, room.isOneToOne).getOrThrow() }.runCatchingUpdatingState(roomNotificationSettings) @@ -161,7 +165,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( private fun CoroutineScope.getDefaultRoomNotificationMode( defaultRoomNotificationMode: MutableState ) = launch { - val isEncrypted = room.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() + val isEncrypted = room.info.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() defaultRoomNotificationMode.value = notificationSettingsService.getDefaultRoomNotificationMode( isEncrypted, room.isOneToOne diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt index c494443f1bf..99aad6b4a4c 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt @@ -9,6 +9,8 @@ package io.element.android.features.roomdetails.impl.securityandprivacy.editroom import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -24,6 +26,7 @@ import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper import io.element.android.libraries.matrix.api.roomAliasFromName import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidity @@ -45,15 +48,16 @@ class EditRoomAddressPresenter @AssistedInject constructor( @Composable override fun present(): EditRoomAddressState { val coroutineScope = rememberCoroutineScope() + val roomInfo by room.roomInfoFlow.collectAsState() val homeserverName = remember { client.userIdServerName() } val roomAddressValidity = remember { mutableStateOf(RoomAddressValidity.Unknown) } - val savedRoomAddress = remember { room.firstAliasMatching(homeserverName)?.addressName() } + val savedRoomAddress by remember { derivedStateOf { roomInfo.firstAliasMatching(homeserverName)?.addressName() } } val saveAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } var newRoomAddress by remember { mutableStateOf( - savedRoomAddress ?: roomAliasHelper.roomAliasNameFromRoomDisplayName(room.displayName) + savedRoomAddress ?: roomAliasHelper.roomAliasNameFromRoomDisplayName(roomInfo.name.orEmpty()) ) } @@ -97,8 +101,9 @@ class EditRoomAddressPresenter @AssistedInject constructor( newRoomAddress: String, ) = launch { suspend { - val savedCanonicalAlias = room.canonicalAlias - val savedAliasFromHomeserver = room.firstAliasMatching(serverName) + val roomInfo = room.info + val savedCanonicalAlias = roomInfo.canonicalAlias + val savedAliasFromHomeserver = roomInfo.firstAliasMatching(serverName) val newRoomAlias = client.roomAliasFromName(newRoomAddress) ?: throw IllegalArgumentException("Invalid room address") // First publish the new alias in the room directory @@ -112,7 +117,7 @@ class EditRoomAddressPresenter @AssistedInject constructor( when { // Allow to update the canonical alias only if the saved canonical alias matches the homeserver or if there is no canonical alias savedCanonicalAlias == null || savedCanonicalAlias.matchesServer(serverName) -> { - val newAlternativeAliases = room.alternativeAliases.filter { it != savedAliasFromHomeserver } + val newAlternativeAliases = roomInfo.alternativeAliases.filter { it != savedAliasFromHomeserver } room.updateCanonicalAlias(newRoomAlias, newAlternativeAliases).getOrThrow() } // Otherwise, only update the alternative aliases and keep the current canonical alias @@ -121,7 +126,7 @@ class EditRoomAddressPresenter @AssistedInject constructor( // New alias is added first, so we make sure we pick it first add(newRoomAlias) // Add all other aliases, except the one we just removed from the room directory - addAll(room.alternativeAliases.filter { it != savedAliasFromHomeserver }) + addAll(roomInfo.alternativeAliases.filter { it != savedAliasFromHomeserver }) } room.updateCanonicalAlias(savedCanonicalAlias, newAlternativeAliases).getOrThrow() } @@ -134,7 +139,7 @@ class EditRoomAddressPresenter @AssistedInject constructor( /** * Returns the first alias that matches the given server name, or null if none match. */ -private fun MatrixRoom.firstAliasMatching(serverName: String): RoomAlias? { +private fun MatrixRoomInfo.firstAliasMatching(serverName: String): RoomAlias? { // Check if the canonical alias matches the homeserver if (canonicalAlias?.matchesServer(serverName) == true) { return canonicalAlias diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt index d28dd00c35a..ee2d8c82c72 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt @@ -9,9 +9,8 @@ package io.element.android.libraries.matrix.api.analytics import im.vector.app.features.analytics.plan.ViewRoom import io.element.android.libraries.matrix.api.room.MatrixRoom -import kotlinx.coroutines.flow.first -suspend fun MatrixRoom.toAnalyticsViewRoom( +fun MatrixRoom.toAnalyticsViewRoom( trigger: ViewRoom.Trigger? = null, selectedSpace: MatrixRoom? = null, viaKeyboard: Boolean? = null, @@ -19,8 +18,8 @@ suspend fun MatrixRoom.toAnalyticsViewRoom( val activeSpace = selectedSpace?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home return ViewRoom( - isDM = roomInfoFlow.first().isDirect, - isSpace = isSpace, + isDM = info.isDirect, + isSpace = info.isSpace, trigger = trigger, activeSpace = activeSpace, viaKeyboard = viaKeyboard @@ -28,5 +27,5 @@ suspend fun MatrixRoom.toAnalyticsViewRoom( } private fun MatrixRoom.toActiveSpace(): ViewRoom.ActiveSpace { - return if (isPublic) ViewRoom.ActiveSpace.Public else ViewRoom.ActiveSpace.Private + return if (info.isPublic) ViewRoom.ActiveSpace.Public else ViewRoom.ActiveSpace.Private } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index e75c90765c5..626824fc298 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -45,23 +45,15 @@ import java.io.File interface MatrixRoom : Closeable { val sessionId: SessionId val roomId: RoomId - val displayName: String - val canonicalAlias: RoomAlias? - val alternativeAliases: List - val topic: String? - val avatarUrl: String? - val isEncrypted: Boolean? - val isSpace: Boolean - val isPublic: Boolean - val activeMemberCount: Long - val joinedMemberCount: Long val roomCoroutineScope: CoroutineScope - val roomInfoFlow: Flow + val roomInfoFlow: StateFlow val roomTypingMembersFlow: Flow> val identityStateChangesFlow: Flow> + val info: MatrixRoomInfo get() = roomInfoFlow.value + /** * The current knock requests in the room as a Flow. */ @@ -71,7 +63,7 @@ interface MatrixRoom : Closeable { * A one-to-one is a room with exactly 2 members. * See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/#default-underride-rules). */ - val isOneToOne: Boolean get() = activeMemberCount == 2L + val isOneToOne: Boolean get() = info.activeMembersCount == 2L /** * The current loaded members as a StateFlow. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt index f9158ae3601..6361f2fd2a4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt @@ -19,7 +19,7 @@ fun MatrixRoom.matches(roomIdOrAlias: RoomIdOrAlias): Boolean { roomIdOrAlias.roomId == roomId } is RoomIdOrAlias.Alias -> { - roomIdOrAlias.roomAlias == canonicalAlias || roomIdOrAlias.roomAlias in alternativeAliases + roomIdOrAlias.roomAlias == info.canonicalAlias || roomIdOrAlias.roomAlias in info.alternativeAliases } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt index ba74c16a264..de72a54469e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt @@ -70,6 +70,44 @@ class MatrixRoomInfoMapper { historyVisibility = it.historyVisibility.map(), ) } + +// fun map(rustRoom: Room): MatrixRoomInfo = with(rustRoom) { +// return MatrixRoomInfo( +// id = RoomId(id()), +// name = rawName(), +// rawName = displayName(), +// topic = topic(), +// avatarUrl = avatarUrl(), +// isPublic = isPublic(), +// isDirect = null, +// isEncrypted = encryptionState() == EncryptionState.ENCRYPTED, +// joinRule = null, +// isSpace = isSpace(), +// isTombstoned = isTombstoned(), +// isFavorite = null, +// canonicalAlias = canonicalAlias()?.let(::RoomAlias), +// alternativeAliases = alternativeAliases().map(::RoomAlias).toImmutableList(), +// currentUserMembership = membership().map(), +// inviter = null, +// activeMembersCount = activeMembersCount().toLong(), +// invitedMembersCount = invitedMembersCount().toLong(), +// joinedMembersCount = joinedMembersCount().toLong(), +// userPowerLevels = persistentMapOf(), +// highlightCount = 0, +// notificationCount = 0, +// userDefinedNotificationMode = null, +// hasRoomCall = hasActiveRoomCall(), +// activeRoomCallParticipants = activeRoomCallParticipants().map(::UserId).toImmutableList(), +// isMarkedUnread = false, +// numUnreadMessages = 0, +// numUnreadNotifications = 0, +// numUnreadMentions = 0, +// heroes = heroes().map(RoomHero::map).toImmutableList(), +// pinnedEventIds = persistentListOf(), +// creator = null, +// historyVisibility = null, +// ) +// } } fun RustMembership.map(): CurrentUserMembership = when (this) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index e68357041f3..5b742e83f86 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -83,14 +83,13 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.DateDividerMode import org.matrix.rustcomponents.sdk.IdentityStatusChangeListener import org.matrix.rustcomponents.sdk.KnockRequestsListener import org.matrix.rustcomponents.sdk.RoomInfo import org.matrix.rustcomponents.sdk.RoomInfoListener -import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.RoomMessageEventMessageType import org.matrix.rustcomponents.sdk.TimelineConfiguration import org.matrix.rustcomponents.sdk.TimelineFilter @@ -115,7 +114,6 @@ import org.matrix.rustcomponents.sdk.Timeline as InnerTimeline class RustMatrixRoom( override val sessionId: SessionId, private val deviceId: DeviceId, - private val roomListItem: RoomListItem, private val innerRoom: InnerRoom, innerTimeline: InnerTimeline, private val notificationSettingsService: NotificationSettingsService, @@ -127,22 +125,17 @@ class RustMatrixRoom( private val matrixRoomInfoMapper: MatrixRoomInfoMapper, private val featureFlagService: FeatureFlagService, private val roomMembershipObserver: RoomMembershipObserver, + initialRoomInfo: MatrixRoomInfo, ) : MatrixRoom { override val roomId = RoomId(innerRoom.id()) - override val roomInfoFlow: Flow = mxCallbackFlow { - runCatching { innerRoom.roomInfo() } - .getOrNull() - ?.let(matrixRoomInfoMapper::map) - ?.let { initial -> - channel.trySend(initial) - } + override val roomInfoFlow: StateFlow = mxCallbackFlow { innerRoom.subscribeToRoomInfoUpdates(object : RoomInfoListener { override fun call(roomInfo: RoomInfo) { channel.trySend(matrixRoomInfoMapper.map(roomInfo)) } }) - }.shareIn(sessionCoroutineScope, started = SharingStarted.Lazily, replay = 1) + }.stateIn(sessionCoroutineScope, started = SharingStarted.Lazily, initialValue = initialRoomInfo) override val roomTypingMembersFlow: Flow> = mxCallbackFlow { val initial = emptyList() @@ -310,42 +303,6 @@ class RustMatrixRoom( liveTimeline.close() } - override val displayName: String - get() = runCatching { innerRoom.displayName() ?: "" }.getOrDefault("") - - override val topic: String? - get() = runCatching { innerRoom.topic() }.getOrDefault(null) - - override val avatarUrl: String? - get() = runCatching { roomListItem.avatarUrl() ?: innerRoom.avatarUrl() }.getOrDefault(null) - - override val isEncrypted: Boolean? - get() = runCatching { - when (innerRoom.encryptionState()) { - EncryptionState.ENCRYPTED -> true - EncryptionState.NOT_ENCRYPTED -> false - EncryptionState.UNKNOWN -> null - } - }.getOrNull() - - override val canonicalAlias: RoomAlias? - get() = runCatching { innerRoom.canonicalAlias()?.let(::RoomAlias) }.getOrDefault(null) - - override val alternativeAliases: List - get() = runCatching { innerRoom.alternativeAliases().map { RoomAlias(it) } }.getOrDefault(emptyList()) - - override val isPublic: Boolean - get() = runCatching { innerRoom.isPublic() }.getOrDefault(false) - - override val isSpace: Boolean - get() = runCatching { innerRoom.isSpace() }.getOrDefault(false) - - override val joinedMemberCount: Long - get() = runCatching { innerRoom.joinedMembersCount().toLong() }.getOrDefault(0) - - override val activeMemberCount: Long - get() = runCatching { innerRoom.activeMembersCount().toLong() }.getOrDefault(0) - override suspend fun updateMembers() { val useCache = membersStateFlow.value is MatrixRoomMembersState.Unknown val source = if (useCache) { @@ -383,7 +340,7 @@ class RustMatrixRoom( val currentRoomNotificationSettings = currentState.roomNotificationSettings() _roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Pending(prevRoomNotificationSettings = currentRoomNotificationSettings) runCatching { - val isEncrypted = isEncrypted ?: getUpdatedIsEncrypted().getOrThrow() + val isEncrypted = roomInfoFlow.value.isEncrypted ?: getUpdatedIsEncrypted().getOrThrow() notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow() }.map { _roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(it) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt index 4488b2c036b..2bcd15c142a 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomFactory.kt @@ -104,10 +104,10 @@ class RustRoomFactory( return@withContext null } val liveTimeline = roomReferences.fullRoom.timeline() + val initialRoomInfo = roomReferences.fullRoom.roomInfo() RustMatrixRoom( sessionId = sessionId, deviceId = deviceId, - roomListItem = roomReferences.roomListItem, innerRoom = roomReferences.fullRoom, innerTimeline = liveTimeline, sessionCoroutineScope = sessionCoroutineScope, @@ -119,6 +119,7 @@ class RustRoomFactory( matrixRoomInfoMapper = matrixRoomInfoMapper, featureFlagService = featureFlagService, roomMembershipObserver = roomMembershipObserver, + initialRoomInfo = matrixRoomInfoMapper.map(initialRoomInfo), ) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index 6617a8b9cbc..ffb633d7073 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -139,7 +139,7 @@ class MediaGalleryPresenter @AssistedInject constructor( } return MediaGalleryState( - roomName = roomInfo?.name ?: room.displayName, + roomName = roomInfo?.name.orEmpty(), mode = mode, groupedMediaItems = groupedMediaItems, mediaBottomSheetState = mediaBottomSheetState, diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt index bf98fdda087..da673a2b6ca 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt @@ -152,7 +152,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( imageUriString = null, imageMimeType = null, threadId = threadId, - roomName = room.displayName, + roomName = room.info.name, roomIsDm = room.isDm(), outGoingMessage = true, ) From 996a2cef04bdd4664f04b8efd9a5f9bd2210db90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 13 Mar 2025 18:30:07 +0100 Subject: [PATCH 08/10] Refactor `MatrixRoom` so it always receives an initial `MatrixRoomInfo` value This value will be used to make `MatrixRoom.roomInfoFlow` a `StateFlow` so we can assume the initial updated Room data will be present --- .../impl/utils/DefaultCallWidgetProvider.kt | 2 +- .../messages/impl/MessagesPresenter.kt | 12 ++-- .../list/PinnedMessagesListPresenter.kt | 2 +- .../impl/timeline/TimelinePresenter.kt | 8 +-- .../messages/impl/MessagesPresenterTest.kt | 1 - .../IdentityChangeStatePresenterTest.kt | 5 +- .../MessageComposerPresenterTest.kt | 2 - .../roomcall/impl/RoomCallStatePresenter.kt | 6 +- .../impl/RoomCallStatePresenterTest.kt | 6 +- .../roomdetails/impl/RoomDetailsPresenter.kt | 6 +- .../RoomNotificationSettingsPresenter.kt | 8 +-- .../RolesAndPermissionsPresenter.kt | 6 +- .../changeroles/ChangeRolesPresenter.kt | 4 +- .../SecurityAndPrivacyPresenter.kt | 13 ++-- .../EditRoomAddressPresenter.kt | 2 +- .../roomdetails/impl/MatrixRoomFixture.kt | 63 ++++++++++++------- .../impl/RoomDetailsPresenterTest.kt | 22 +++---- .../impl/edit/RoomDetailsEditPresenterTest.kt | 28 +++------ .../RoomMembersModerationPresenterTest.kt | 25 ++++---- .../SecurityAndPrivacyPresenterTest.kt | 29 +++++---- .../EditRoomAddressPresenterTest.kt | 15 +++-- .../IncomingVerificationPresenterTest.kt | 16 ++++- .../matrix/api/analytics/ViewRoomExt.kt | 6 +- .../libraries/matrix/api/room/MatrixRoom.kt | 9 ++- .../matrix/api/room/alias/MatrixRoomAlias.kt | 2 +- .../matrix/test/room/FakeMatrixRoom.kt | 22 +++---- .../matrix/ui/room/MatrixRoomMembers.kt | 6 +- .../matrix/ui/room/MatrixRoomState.kt | 16 ++--- .../matrix/ui/room/MatrixRoomMembersTest.kt | 44 +++++-------- .../impl/gallery/MediaGalleryPresenter.kt | 4 +- .../impl/gallery/MediaGalleryPresenterTest.kt | 11 ++-- .../NotificationBroadcastReceiverHandler.kt | 2 +- 32 files changed, 200 insertions(+), 203 deletions(-) diff --git a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt index 4f2ae0693f1..8d34cfcfd44 100644 --- a/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt +++ b/features/call/impl/src/main/kotlin/io/element/android/features/call/impl/utils/DefaultCallWidgetProvider.kt @@ -38,7 +38,7 @@ class DefaultCallWidgetProvider @Inject constructor( val baseUrl = appPreferencesStore.getCustomElementCallBaseUrlFlow().firstOrNull() ?: elementCallBaseUrlProvider.provides(matrixClient) ?: ElementCallConfig.DEFAULT_BASE_URL - val isEncrypted = room.info.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() + val isEncrypted = room.info().isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() val widgetSettings = callWidgetSettingsProvider.provide(baseUrl, encrypted = isEncrypted) val callUrl = room.generateWidgetWebViewUrl( widgetSettings = widgetSettings, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 9a4b7b913ee..72fa4d51ec6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -128,7 +128,7 @@ class MessagesPresenter @AssistedInject constructor( override fun present(): MessagesState { htmlConverterProvider.Update(currentUserId = room.sessionId) - val roomInfo by room.roomInfoFlow.collectAsState(null) + val roomInfo by room.roomInfoFlow.collectAsState() val localCoroutineScope = rememberCoroutineScope() val composerState = composerPresenter.present() val voiceMessageComposerState = voiceMessageComposerPresenter.present() @@ -147,13 +147,13 @@ class MessagesPresenter @AssistedInject constructor( val userEventPermissions by userEventPermissions(syncUpdateFlow.value) val roomName: AsyncData by remember { - derivedStateOf { roomInfo?.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized } + derivedStateOf { roomInfo.name?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized } } val roomAvatar: AsyncData by remember { - derivedStateOf { roomInfo?.avatarData()?.let { AsyncData.Success(it) } ?: AsyncData.Uninitialized } + derivedStateOf { AsyncData.Success(roomInfo.avatarData()) } } val heroes by remember { - derivedStateOf { roomInfo?.heroes().orEmpty().toPersistentList() } + derivedStateOf { roomInfo.heroes().toPersistentList() } } var hasDismissedInviteDialog by rememberSaveable { @@ -172,7 +172,7 @@ class MessagesPresenter @AssistedInject constructor( val composerHasFocus by remember { derivedStateOf { composerState.textEditorState.hasFocus() } } LaunchedEffect(hasDismissedInviteDialog, composerHasFocus, roomInfo) { withContext(dispatchers.io) { - showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo?.isDm == true && roomInfo?.activeMembersCount == 1L + showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo.isDm && roomInfo.activeMembersCount == 1L } } val isOnline by syncService.isOnline().collectAsState() @@ -276,7 +276,7 @@ class MessagesPresenter @AssistedInject constructor( return AvatarData( id = id.value, name = name, - url = avatarUrl ?: room.info.avatarUrl, + url = avatarUrl ?: room.info().avatarUrl, size = AvatarSize.TimelineRoom ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt index 322c283f003..9eed64ca2d5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -90,7 +90,7 @@ class PinnedMessagesListPresenter @AssistedInject constructor( val timelineRoomInfo = remember(isDm) { TimelineRoomInfo( isDm = isDm, - name = room.info.name, + name = room.info().name, // We don't need to compute those values userHasPermissionToSendMessage = false, userHasPermissionToSendReaction = false, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index c1fefc46854..4061be78c14 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -96,7 +96,7 @@ class TimelinePresenter @AssistedInject constructor( val lastReadReceiptId = rememberSaveable { mutableStateOf(null) } - val roomInfo by room.roomInfoFlow.collectAsState(initial = null) + val roomInfo by room.roomInfoFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() @@ -235,12 +235,12 @@ class TimelinePresenter @AssistedInject constructor( val timelineRoomInfo by remember(typingNotificationState, roomCallState, roomInfo) { derivedStateOf { TimelineRoomInfo( - name = roomInfo?.name, - isDm = roomInfo?.isDm.orFalse(), + name = roomInfo.name, + isDm = roomInfo.isDm.orFalse(), userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendReaction = userHasPermissionToSendReaction, roomCallState = roomCallState, - pinnedEventIds = roomInfo?.pinnedEventIds.orEmpty(), + pinnedEventIds = roomInfo.pinnedEventIds.orEmpty(), typingNotificationState = typingNotificationState, ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 38f3fa8cdb4..3eac4330632 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -593,7 +593,6 @@ class MessagesPresenterTest { fun `present - doesn't show reinvite prompt if other party is present`() = runTest { val room = FakeMatrixRoom( sessionId = A_SESSION_ID, - activeMemberCount = 2L, canUserSendMessageResult = { _, _ -> Result.success(true) }, canRedactOwnResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) }, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt index 2bbf6b7c245..2a82db690e3 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/crypto/identity/IdentityChangeStatePresenterTest.kt @@ -70,10 +70,7 @@ class IdentityChangeStatePresenterTest { @Test fun `present - when the clear room emits identity change, the presenter does not emit new state`() = runTest { - val room = FakeMatrixRoom( - isEncrypted = false, - enableEncryptionResult = { Result.success(Unit) } - ) + val room = FakeMatrixRoom(enableEncryptionResult = { Result.success(Unit) }) val presenter = createIdentityChangeStatePresenter(room) presenter.test { val initialState = awaitItem() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt index e4781da70ef..07205590c81 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/messagecomposer/MessageComposerPresenterTest.kt @@ -1061,8 +1061,6 @@ class MessageComposerPresenterTest { val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN) val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) val room = FakeMatrixRoom( - activeMemberCount = 2, - isEncrypted = true, canUserTriggerRoomNotificationResult = { Result.success(true) }, typingNoticeResult = { Result.success(Unit) } ).apply { diff --git a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt index 4c3ddddb174..d04d40b4ef2 100644 --- a/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt +++ b/features/roomcall/impl/src/main/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenter.kt @@ -26,12 +26,12 @@ class RoomCallStatePresenter @Inject constructor( ) : Presenter { @Composable override fun present(): RoomCallState { - val roomInfo by room.roomInfoFlow.collectAsState(null) + val roomInfo by room.roomInfoFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val canJoinCall by room.canCall(updateKey = syncUpdateFlow.value) val isUserInTheCall by remember { derivedStateOf { - room.sessionId in roomInfo?.activeRoomCallParticipants.orEmpty() + room.sessionId in roomInfo.activeRoomCallParticipants } } val currentCall by currentCallService.currentCall.collectAsState() @@ -41,7 +41,7 @@ class RoomCallStatePresenter @Inject constructor( } } val callState = when { - roomInfo?.hasRoomCall == true -> RoomCallState.OnGoing( + roomInfo.hasRoomCall -> RoomCallState.OnGoing( canJoinCall = canJoinCall, isUserInTheCall = isUserInTheCall, isUserLocallyInTheCall = isUserLocallyInTheCall, diff --git a/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt b/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt index 55559294567..23791eb4441 100644 --- a/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt +++ b/features/roomcall/impl/src/test/kotlin/io/element/android/features/roomcall/impl/RoomCallStatePresenterTest.kt @@ -58,12 +58,10 @@ class RoomCallStatePresenterTest { fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest { val room = FakeMatrixRoom( canUserJoinCallResult = { Result.success(false) }, - ).apply { - givenRoomInfo(aRoomInfo(hasRoomCall = true)) - } + initialRoomInfo = aRoomInfo(hasRoomCall = true), + ) val presenter = createRoomCallStatePresenter(matrixRoom = room) presenter.test { - skipItems(1) assertThat(awaitItem()).isEqualTo( RoomCallState.OnGoing( canJoinCall = false, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index fc6b6e4ce4c..df1daf63867 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -25,7 +25,6 @@ import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags @@ -36,7 +35,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.StateEventType -import io.element.android.libraries.matrix.api.room.isDm import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.api.room.powerlevels.canInvite import io.element.android.libraries.matrix.api.room.powerlevels.canSendState @@ -101,7 +99,7 @@ class RoomDetailsPresenter @Inject constructor( val membersState by room.membersStateFlow.collectAsState() val canInvite by getCanInvite(membersState) - val canonicalAlias by remember { derivedStateOf { roomInfo.canonicalAlias }} + val canonicalAlias by remember { derivedStateOf { roomInfo.canonicalAlias } } val isEncrypted by remember { derivedStateOf { roomInfo.isEncrypted == true } } val isDm by room.isDmAsState() val canEditName by getCanSendState(membersState, StateEventType.ROOM_NAME) @@ -186,7 +184,7 @@ class RoomDetailsPresenter @Inject constructor( isFavorite = isFavorite, displayRolesAndPermissionsSettings = !isDm && isUserAdmin, isPublic = joinRule == JoinRule.Public, - heroes = roomInfo?.heroes.orEmpty().toPersistentList(), + heroes = roomInfo.heroes.toPersistentList(), canShowPinnedMessages = canShowPinnedMessages, canShowMediaGallery = canShowMediaGallery, pinnedMessagesCount = pinnedMessagesCount, diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt index 8f5e24dfc95..7dae51a1a21 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/notificationsettings/RoomNotificationSettingsPresenter.kt @@ -79,11 +79,11 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( mutableStateOf(null) } - val displayName by produceState(room.info.name) { + val displayName by produceState(room.info().name) { room.roomInfoFlow.collect { value = it.name } } - val isRoomEncrypted by produceState(room.info.isEncrypted) { + val isRoomEncrypted by produceState(room.info().isEncrypted) { room.roomInfoFlow.collect { value = it.isEncrypted } } @@ -156,7 +156,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( roomNotificationSettings: MutableState> ) = launch { suspend { - val isEncrypted = room.info.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() + val isEncrypted = room.info().isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() pendingModeState.value = null notificationSettingsService.getRoomNotificationSettings(room.roomId, isEncrypted, room.isOneToOne).getOrThrow() }.runCatchingUpdatingState(roomNotificationSettings) @@ -165,7 +165,7 @@ class RoomNotificationSettingsPresenter @AssistedInject constructor( private fun CoroutineScope.getDefaultRoomNotificationMode( defaultRoomNotificationMode: MutableState ) = launch { - val isEncrypted = room.info.isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() + val isEncrypted = room.info().isEncrypted ?: room.getUpdatedIsEncrypted().getOrThrow() defaultRoomNotificationMode.value = notificationSettingsService.getDefaultRoomNotificationMode( isEncrypted, room.isOneToOne diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt index 5cd4bd17376..37e72d158f0 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/RolesAndPermissionsPresenter.kt @@ -39,7 +39,7 @@ class RolesAndPermissionsPresenter @Inject constructor( @Composable override fun present(): RolesAndPermissionsState { val coroutineScope = rememberCoroutineScope() - val roomInfo by room.roomInfoFlow.collectAsState(initial = null) + val roomInfo by room.roomInfoFlow.collectAsState() val roomMembers by room.membersStateFlow.collectAsState() // Get the list of active room members (joined or invited), in order to filter members present in the power // level state Event. @@ -109,8 +109,8 @@ class RolesAndPermissionsPresenter @Inject constructor( } } - private fun MatrixRoomInfo?.userCountWithRole(userIds: List, role: RoomMember.Role): Int { - return this?.userPowerLevels.orEmpty().count { (userId, level) -> + private fun MatrixRoomInfo.userCountWithRole(userIds: List, role: RoomMember.Role): Int { + return this.userPowerLevels.count { (userId, level) -> RoomMember.Role.forPowerLevel(level) == role && userId in userIds } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt index 392f9da0961..8878dc8876a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/rolesandpermissions/changeroles/ChangeRolesPresenter.kt @@ -106,10 +106,10 @@ class ChangeRolesPresenter @AssistedInject constructor( val hasPendingChanges = usersWithRole.value != selectedUsers.value - val roomInfo by room.roomInfoFlow.collectAsState(initial = null) + val roomInfo by room.roomInfoFlow.collectAsState() fun canChangeMemberRole(userId: UserId): Boolean { // An admin can't remove or demote another admin - val powerLevel = roomInfo?.userPowerLevels?.get(userId) ?: 0L + val powerLevel = roomInfo.userPowerLevels[userId] ?: 0L return RoomMember.Role.forPowerLevel(powerLevel) != RoomMember.Role.ADMIN } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt index 4a2c67d9146..d6f27a9a5bb 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenter.kt @@ -57,9 +57,7 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor( val saveAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } val homeserverName = remember { matrixClient.userIdServerName() } val syncUpdateFlow = room.syncUpdateFlow.collectAsState() - val roomInfo = room.roomInfoFlow.collectAsState(null) - - val isEncrypted by remember { derivedStateOf { roomInfo.value?.isEncrypted == true } } + val roomInfo by room.roomInfoFlow.collectAsState() val savedIsVisibleInRoomDirectory = remember { mutableStateOf>(AsyncData.Uninitialized) } LaunchedEffect(Unit) { @@ -68,12 +66,13 @@ class SecurityAndPrivacyPresenter @AssistedInject constructor( val savedSettings by remember { derivedStateOf { + val historyVisibility = roomInfo.historyVisibility.map() SecurityAndPrivacySettings( - roomAccess = roomInfo.value?.joinRule.map(), - isEncrypted = isEncrypted, + roomAccess = roomInfo.joinRule.map(), + isEncrypted = roomInfo.isEncrypted == true, isVisibleInRoomDirectory = savedIsVisibleInRoomDirectory.value, - historyVisibility = roomInfo.value?.historyVisibility.map(), - address = roomInfo.value?.firstDisplayableAlias(homeserverName)?.value, + historyVisibility = historyVisibility, + address = roomInfo.firstDisplayableAlias(homeserverName)?.value, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt index 99aad6b4a4c..2c581e45842 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenter.kt @@ -101,7 +101,7 @@ class EditRoomAddressPresenter @AssistedInject constructor( newRoomAddress: String, ) = launch { suspend { - val roomInfo = room.info + val roomInfo = room.info() val savedCanonicalAlias = roomInfo.canonicalAlias val savedAliasFromHomeserver = roomInfo.firstAliasMatching(serverName) val newRoomAlias = client.roomAliasFromName(newRoomAddress) ?: throw IllegalArgumentException("Invalid room address") diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt index 0c418c0721b..3cf5c02e47f 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/MatrixRoomFixture.kt @@ -7,34 +7,43 @@ package io.element.android.features.roomdetails.impl +import io.element.android.libraries.matrix.api.core.RoomAlias import io.element.android.libraries.matrix.api.core.RoomId +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.room.RoomMember import io.element.android.libraries.matrix.api.room.StateEventType import io.element.android.libraries.matrix.api.room.join.JoinRule import io.element.android.libraries.matrix.test.AN_AVATAR_URL +import io.element.android.libraries.matrix.test.A_ROOM_ALIAS import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_ROOM_TOPIC +import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.tests.testutils.lambda.lambdaError fun aMatrixRoom( + sessionId: SessionId = A_SESSION_ID, roomId: RoomId = A_ROOM_ID, displayName: String = A_ROOM_NAME, rawName: String? = displayName, topic: String? = A_ROOM_TOPIC, avatarUrl: String? = AN_AVATAR_URL, + canonicalAlias: RoomAlias? = A_ROOM_ALIAS, isEncrypted: Boolean = true, isPublic: Boolean = true, isDirect: Boolean = false, joinRule: JoinRule? = null, + activeMemberCount: Long = 1, + joinedMemberCount: Long = 1, + invitedMemberCount: Long = 0, notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), - emitRoomInfo: Boolean = false, canInviteResult: (UserId) -> Result = { lambdaError() }, canBanResult: (UserId) -> Result = { lambdaError() }, + canKickResult: (UserId) -> Result = { lambdaError() }, canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() }, userDisplayNameResult: (UserId) -> Result = { lambdaError() }, userAvatarUrlResult: () -> Result = { lambdaError() }, @@ -44,16 +53,20 @@ fun aMatrixRoom( removeAvatarResult: () -> Result = { lambdaError() }, canUserJoinCallResult: (UserId) -> Result = { lambdaError() }, getUpdatedMemberResult: (UserId) -> Result = { lambdaError() }, + userRoleResult: () -> Result = { lambdaError() }, + kickUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() }, + banUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() }, + unBanUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() }, + updateCanonicalAliasResult: (RoomAlias?, List) -> Result = { _, _ -> lambdaError() }, + publishRoomAliasInRoomDirectoryResult: (RoomAlias) -> Result = { lambdaError() }, + removeRoomAliasFromRoomDirectoryResult: (RoomAlias) -> Result = { lambdaError() }, ) = FakeMatrixRoom( + sessionId = sessionId, roomId = roomId, - displayName = displayName, - topic = topic, - avatarUrl = avatarUrl, - isEncrypted = isEncrypted, - isPublic = isPublic, notificationSettingsService = notificationSettingsService, canInviteResult = canInviteResult, canBanResult = canBanResult, + canKickResult = canKickResult, canSendStateResult = canSendStateResult, userDisplayNameResult = userDisplayNameResult, userAvatarUrlResult = userAvatarUrlResult, @@ -63,19 +76,25 @@ fun aMatrixRoom( removeAvatarResult = removeAvatarResult, canUserJoinCallResult = canUserJoinCallResult, getUpdatedMemberResult = getUpdatedMemberResult, -).apply { - if (emitRoomInfo) { - givenRoomInfo( - aRoomInfo( - name = displayName, - rawName = rawName, - topic = topic, - avatarUrl = avatarUrl, - isDirect = isDirect, - isPublic = isPublic, - isEncrypted = isEncrypted, - joinRule = joinRule, - ) - ) - } -} + userRoleResult = userRoleResult, + kickUserResult = kickUserResult, + banUserResult = banUserResult, + unBanUserResult = unBanUserResult, + updateCanonicalAliasResult = updateCanonicalAliasResult, + publishRoomAliasInRoomDirectoryResult = publishRoomAliasInRoomDirectoryResult, + removeRoomAliasFromRoomDirectoryResult = removeRoomAliasFromRoomDirectoryResult, + initialRoomInfo = aRoomInfo( + name = displayName, + rawName = rawName, + topic = topic, + avatarUrl = avatarUrl, + canonicalAlias = canonicalAlias, + isDirect = isDirect, + isPublic = isPublic, + isEncrypted = isEncrypted, + joinRule = joinRule, + joinedMembersCount = joinedMemberCount, + activeMembersCount = activeMemberCount, + invitedMembersCount = invitedMemberCount, + ) +) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt index 53799c7c926..07d10ede633 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenterTest.kt @@ -110,7 +110,7 @@ class RoomDetailsPresenterTest { } @Test - fun `present - initial state is created from room if roomInfo is null`() = runTest { + fun `present - initial state is created from initial room info`() = runTest { val room = aMatrixRoom( canInviteResult = { Result.success(true) }, canUserJoinCallResult = { Result.success(true) }, @@ -118,21 +118,22 @@ class RoomDetailsPresenterTest { ) val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { - skipItems(1) val initialState = awaitItem() assertThat(initialState.roomId).isEqualTo(room.roomId) - assertThat(initialState.roomName).isEqualTo(room.displayName) - assertThat(initialState.roomAvatarUrl).isEqualTo(room.avatarUrl) - assertThat(initialState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.topic!!)) - assertThat(initialState.memberCount).isEqualTo(room.joinedMemberCount) + assertThat(initialState.roomName).isEqualTo(room.info().name) + assertThat(initialState.roomAvatarUrl).isEqualTo(room.info().avatarUrl) + assertThat(initialState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.info().topic!!)) + assertThat(initialState.memberCount).isEqualTo(room.info().joinedMembersCount) assertThat(initialState.canShowPinnedMessages).isTrue() - assertThat(initialState.pinnedMessagesCount).isNull() + assertThat(initialState.pinnedMessagesCount).isEqualTo(0) assertThat(initialState.canShowSecurityAndPrivacy).isFalse() + + cancelAndIgnoreRemainingEvents() } } @Test - fun `present - initial state is updated with roomInfo if it exists`() = runTest { + fun `present - initial state is updated with a new roomInfo`() = runTest { val roomInfo = aRoomInfo( name = A_ROOM_NAME, topic = A_ROOM_TOPIC, @@ -169,7 +170,7 @@ class RoomDetailsPresenterTest { val presenter = createRoomDetailsPresenter(room) presenter.testWithLifecycleOwner(lifecycleOwner = fakeLifecycleOwner) { val initialState = awaitItem() - assertThat(initialState.roomName).isEqualTo(room.displayName) + assertThat(initialState.roomName).isEqualTo(room.info().name) cancelAndIgnoreRemainingEvents() } @@ -327,7 +328,7 @@ class RoomDetailsPresenterTest { val settledState = awaitItem() assertThat(settledState.canEdit).isFalse() // If there is a topic, it's visible - assertThat(settledState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.topic!!)) + assertThat(settledState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.info().topic!!)) cancelAndIgnoreRemainingEvents() } @@ -646,7 +647,6 @@ class RoomDetailsPresenterTest { @Test fun `present - show knock requests`() = runTest { val room = aMatrixRoom( - emitRoomInfo = true, canInviteResult = { Result.success(true) }, canUserJoinCallResult = { Result.success(true) }, canSendStateResult = { _, _ -> Result.success(true) }, diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt index 26c9f99105d..763f94c3100 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/edit/RoomDetailsEditPresenterTest.kt @@ -93,7 +93,6 @@ class RoomDetailsEditPresenterTest { avatarUrl = AN_AVATAR_URL, displayName = A_ROOM_NAME, rawName = A_ROOM_RAW_NAME, - emitRoomInfo = true, canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} @@ -106,7 +105,7 @@ class RoomDetailsEditPresenterTest { assertThat(initialState.roomId).isEqualTo(room.roomId) assertThat(initialState.roomRawName).isEqualTo(A_ROOM_RAW_NAME) assertThat(initialState.roomAvatarUrl).isEqualTo(roomAvatarUri) - assertThat(initialState.roomTopic).isEqualTo(room.topic.orEmpty()) + assertThat(initialState.roomTopic).isEqualTo(room.info().topic.orEmpty()) assertThat(initialState.avatarActions).containsExactly( AvatarAction.ChoosePhoto, AvatarAction.TakePhoto, @@ -220,7 +219,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, canSendStateResult = { _, _ -> Result.success(true) } ) val deleteCallback = lambdaRecorder {} @@ -266,7 +264,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(anotherAvatarUri) @@ -291,7 +288,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(anotherAvatarUri) @@ -319,8 +315,7 @@ class RoomDetailsEditPresenterTest { stateWithNewAvatar.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.TakePhoto)) val stateWithNewAvatar2 = awaitItem() assertThat(stateWithNewAvatar2.roomAvatarUrl).isEqualTo(roomAvatarUri) - deleteCallback.assertions().isCalledExactly(4).withSequence( - listOf(value(null)), + deleteCallback.assertions().isCalledExactly(3).withSequence( listOf(value(null)), listOf(value(roomAvatarUri)), listOf(value(anotherAvatarUri)), @@ -334,7 +329,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(roomAvatarUri) @@ -385,7 +379,6 @@ class RoomDetailsEditPresenterTest { topic = null, displayName = "fallback", avatarUrl = null, - emitRoomInfo = true, canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(roomAvatarUri) @@ -439,7 +432,6 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, setNameResult = setNameResult, setTopicResult = setTopicResult, removeAvatarResult = removeAvatarResult, @@ -553,7 +545,7 @@ class RoomDetailsEditPresenterTest { updateAvatarResult.assertions().isCalledOnce().with(value(MimeTypes.Jpeg), value(fakeFileContents)) deleteCallback.assertions().isCalledExactly(2).withSequence( listOf(value(null)), - listOf(value(null)), + listOf(value(roomAvatarUri)), ) } } @@ -588,11 +580,10 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, setNameResult = { Result.failure(Throwable("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name")) + saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name"), deleteCallbackNumberOfInvocation = 1) } @Test @@ -601,11 +592,10 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, setTopicResult = { Result.failure(Throwable("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic")) + saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic"), deleteCallbackNumberOfInvocation = 1) } @Test @@ -614,11 +604,10 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, removeAvatarResult = { Result.failure(Throwable("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove), deleteCallbackNumberOfInvocation = 3) + saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove), deleteCallbackNumberOfInvocation = 2) } @Test @@ -628,11 +617,10 @@ class RoomDetailsEditPresenterTest { topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, - emitRoomInfo = true, updateAvatarResult = { _, _ -> Result.failure(Throwable("!")) }, canSendStateResult = { _, _ -> Result.success(true) } ) - saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto), deleteCallbackNumberOfInvocation = 3) + saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto), deleteCallbackNumberOfInvocation = 2) } @Test @@ -704,6 +692,6 @@ class RoomDetailsEditPresenterTest { } private suspend fun ReceiveTurbine.awaitFirstItem(): T { - skipItems(2) + skipItems(1) return awaitItem() } diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt index ca54890d271..d86fc499251 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/moderation/RoomMembersModerationPresenterTest.kt @@ -12,6 +12,7 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.RoomModeration +import io.element.android.features.roomdetails.impl.aMatrixRoom import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.aVictor import io.element.android.libraries.architecture.AsyncAction @@ -34,7 +35,7 @@ import org.junit.Test class RoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when room is DM is false`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( isPublic = true, activeMemberCount = 2, canKickResult = { Result.success(true) }, @@ -51,7 +52,7 @@ class RoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when user can kick other users, FF is enabled and room is not a DM returns true`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( activeMemberCount = 10, canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, @@ -66,7 +67,7 @@ class RoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when user can ban other users, FF is enabled and room is not a DM returns true`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( activeMemberCount = 10, canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, @@ -81,7 +82,7 @@ class RoomMembersModerationPresenterTest { @Test fun `present - SelectRoomMember when the current user has permissions displays member actions`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, @@ -107,7 +108,7 @@ class RoomMembersModerationPresenterTest { @Test fun `present - SelectRoomMember displays only view profile if selected member has same power level as the current user`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( sessionId = A_USER_ID, canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, @@ -133,7 +134,7 @@ class RoomMembersModerationPresenterTest { @Test fun `present - SelectRoomMember displays an unban confirmation dialog when the member is banned`() = runTest { val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN) - val room = FakeMatrixRoom( + val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, @@ -154,7 +155,7 @@ class RoomMembersModerationPresenterTest { @Test fun `present - Kick removes the user`() = runTest { val analyticsService = FakeAnalyticsService() - val room = FakeMatrixRoom( + val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, @@ -183,7 +184,7 @@ class RoomMembersModerationPresenterTest { @Test fun `present - BanUser requires confirmation and then bans the user`() = runTest { val analyticsService = FakeAnalyticsService() - val room = FakeMatrixRoom( + val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, @@ -219,7 +220,7 @@ class RoomMembersModerationPresenterTest { fun `present - UnbanUser requires confirmation and then unbans the user`() = runTest { val analyticsService = FakeAnalyticsService() val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN) - val room = FakeMatrixRoom( + val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, @@ -251,7 +252,7 @@ class RoomMembersModerationPresenterTest { @Test fun `present - Reset removes the selected user and actions`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, userRoleResult = { Result.success(RoomMember.Role.USER) }, @@ -273,7 +274,7 @@ class RoomMembersModerationPresenterTest { @Test fun `present - Reset resets any async actions`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( canKickResult = { Result.success(true) }, canBanResult = { Result.success(true) }, kickUserResult = { _, _ -> Result.failure(Throwable("Eek")) }, @@ -321,7 +322,7 @@ class RoomMembersModerationPresenterTest { } private fun TestScope.createRoomMembersModerationPresenter( - matrixRoom: FakeMatrixRoom = FakeMatrixRoom(), + matrixRoom: FakeMatrixRoom = aMatrixRoom(), dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ): RoomMembersModerationPresenter { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt index 11e3332f3f1..1c419c9d1f4 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/SecurityAndPrivacyPresenterTest.kt @@ -56,17 +56,15 @@ class SecurityAndPrivacyPresenterTest { fun `present - room info change updates saved and edited settings`() = runTest { val room = FakeMatrixRoom( canSendStateResult = { _, _ -> Result.success(true) }, + initialRoomInfo = aRoomInfo( + joinRule = JoinRule.Public, + historyVisibility = RoomHistoryVisibility.WorldReadable, + canonicalAlias = A_ROOM_ALIAS, + ) ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { - skipItems(2) - room.givenRoomInfo( - aRoomInfo( - joinRule = JoinRule.Public, - historyVisibility = RoomHistoryVisibility.WorldReadable, - canonicalAlias = A_ROOM_ALIAS, - ) - ) + skipItems(1) with(awaitItem()) { assertThat(editedSettings).isEqualTo(savedSettings) assertThat(editedSettings.roomAccess).isEqualTo(SecurityAndPrivacyRoomAccess.Anyone) @@ -151,6 +149,7 @@ class SecurityAndPrivacyPresenterTest { assertThat(canBeSaved).isTrue() eventSink(SecurityAndPrivacyEvents.ToggleEncryptionState) } + skipItems(1) with(awaitItem()) { assertThat(editedSettings.isEncrypted).isFalse() assertThat(canBeSaved).isFalse() @@ -162,7 +161,8 @@ class SecurityAndPrivacyPresenterTest { fun `present - room visibility loading and change`() = runTest { val room = FakeMatrixRoom( canSendStateResult = { _, _ -> Result.success(true) }, - roomVisibilityResult = { Result.success(RoomVisibility.Private) } + roomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared) ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { @@ -212,7 +212,8 @@ class SecurityAndPrivacyPresenterTest { updateJoinRuleResult = updateJoinRuleLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda, updateRoomHistoryVisibilityResult = updateRoomHistoryVisibilityLambda, - roomVisibilityResult = { Result.success(RoomVisibility.Private) } + roomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(joinRule = JoinRule.Invite, historyVisibility = RoomHistoryVisibility.Shared) ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { @@ -276,7 +277,8 @@ class SecurityAndPrivacyPresenterTest { updateJoinRuleResult = updateJoinRuleLambda, updateRoomVisibilityResult = updateRoomVisibilityLambda, updateRoomHistoryVisibilityResult = updateRoomHistoryVisibilityLambda, - roomVisibilityResult = { Result.success(RoomVisibility.Private) } + roomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) ) val presenter = createSecurityAndPrivacyPresenter(room = room) presenter.test { @@ -312,7 +314,7 @@ class SecurityAndPrivacyPresenterTest { ) ) // Saved settings are updated 2 times to match the edited settings - skipItems(2) + skipItems(3) with(awaitItem()) { assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) assertThat(savedSettings.isVisibleInRoomDirectory).isNotEqualTo(editedSettings.isVisibleInRoomDirectory) @@ -329,7 +331,8 @@ class SecurityAndPrivacyPresenterTest { serverName: String = "matrix.org", room: MatrixRoom = FakeMatrixRoom( canSendStateResult = { _, _ -> Result.success(true) }, - roomVisibilityResult = { Result.success(RoomVisibility.Private) } + roomVisibilityResult = { Result.success(RoomVisibility.Private) }, + initialRoomInfo = aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, joinRule = JoinRule.Private) ), navigator: SecurityAndPrivacyNavigator = FakeSecurityAndPrivacyNavigator(), ): SecurityAndPrivacyPresenter { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenterTest.kt index 8c30f3d4fa4..845e2c02725 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/securityandprivacy/editroomaddress/EditRoomAddressPresenterTest.kt @@ -8,6 +8,7 @@ package io.element.android.features.roomdetails.impl.securityandprivacy.editroomaddress import com.google.common.truth.Truth.assertThat +import io.element.android.features.roomdetails.impl.aMatrixRoom import io.element.android.features.roomdetails.impl.securityandprivacy.FakeSecurityAndPrivacyNavigator import io.element.android.features.roomdetails.impl.securityandprivacy.SecurityAndPrivacyNavigator import io.element.android.libraries.architecture.AsyncAction @@ -31,7 +32,9 @@ import java.util.Optional class EditRoomAddressPresenterTest { @Test fun `present - initial state no address`() = runTest { - val presenter = createEditRoomAddressPresenter() + val presenter = createEditRoomAddressPresenter( + room = aMatrixRoom(displayName = "") + ) presenter.test { with(awaitItem()) { assertThat(homeserverName).isEqualTo("matrix.org") @@ -45,7 +48,7 @@ class EditRoomAddressPresenterTest { @Test fun `present - initial state address matching own homeserver`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( canonicalAlias = RoomAlias("#canonical:matrix.org"), ) val presenter = createEditRoomAddressPresenter(room = room) @@ -62,7 +65,8 @@ class EditRoomAddressPresenterTest { @Test fun `present - initial state address not matching own homeserver`() = runTest { - val room = FakeMatrixRoom( + val room = aMatrixRoom( + displayName = "", canonicalAlias = RoomAlias("#canonical:notmatrix.org"), ) val presenter = createEditRoomAddressPresenter(room = room) @@ -190,7 +194,7 @@ class EditRoomAddressPresenterTest { val navigator = FakeSecurityAndPrivacyNavigator(closeEditRoomAddressLambda = closeEditAddressLambda) val canonicalAlias = RoomAlias("#canonical:matrix.org") - val room = FakeMatrixRoom( + val room = aMatrixRoom( canonicalAlias = canonicalAlias, updateCanonicalAliasResult = updateCanonicalAliasResult, publishRoomAliasInRoomDirectoryResult = publishAliasInRoomDirectoryResult, @@ -240,7 +244,7 @@ class EditRoomAddressPresenterTest { val navigator = FakeSecurityAndPrivacyNavigator(closeEditRoomAddressLambda = closeEditAddressLambda) val canonicalAlias = RoomAlias("#canonical:notmatrix.org") - val room = FakeMatrixRoom( + val room = aMatrixRoom( canonicalAlias = canonicalAlias, updateCanonicalAliasResult = updateCanonicalAliasResult, publishRoomAliasInRoomDirectoryResult = publishAliasInRoomDirectoryResult, @@ -314,6 +318,7 @@ class EditRoomAddressPresenterTest { with(awaitItem()) { eventSink(EditRoomAddressEvents.Save) } + assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Loading::class.java) with(awaitItem()) { assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java) eventSink(EditRoomAddressEvents.DismissError) diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt index 0f453be2bcc..73068bcaaf4 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/incoming/IncomingVerificationPresenterTest.kt @@ -27,10 +27,12 @@ import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.test import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test +import kotlin.time.Duration.Companion.seconds @ExperimentalCoroutinesApi class IncomingVerificationPresenterTest { @@ -61,6 +63,9 @@ class IncomingVerificationPresenterTest { isWaiting = false, ) ) + + advanceTimeBy(1.seconds) + resetLambda.assertions().isCalledOnce().with(value(false)) acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acceptVerificationRequestLambda.assertions().isNeverCalled() @@ -69,7 +74,9 @@ class IncomingVerificationPresenterTest { skipItems(1) val initialWaitingState = awaitItem() assertThat((initialWaitingState.step as IncomingVerificationState.Step.Initial).isWaiting).isTrue() - advanceUntilIdle() + + advanceTimeBy(1.seconds) + acceptVerificationRequestLambda.assertions().isCalledOnce() // Remote sent the data fakeSessionVerificationService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest) @@ -218,6 +225,9 @@ class IncomingVerificationPresenterTest { isWaiting = false, ) ) + + advanceTimeBy(1.seconds) + resetLambda.assertions().isCalledOnce().with(value(false)) acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acceptVerificationRequestLambda.assertions().isNeverCalled() @@ -226,7 +236,9 @@ class IncomingVerificationPresenterTest { skipItems(1) val initialWaitingState = awaitItem() assertThat((initialWaitingState.step as IncomingVerificationState.Step.Initial).isWaiting).isTrue() - advanceUntilIdle() + + advanceTimeBy(1.seconds) + acceptVerificationRequestLambda.assertions().isCalledOnce() // Remote sent the data fakeSessionVerificationService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt index ee2d8c82c72..c221b2881e1 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/analytics/ViewRoomExt.kt @@ -18,8 +18,8 @@ fun MatrixRoom.toAnalyticsViewRoom( val activeSpace = selectedSpace?.toActiveSpace() ?: ViewRoom.ActiveSpace.Home return ViewRoom( - isDM = info.isDirect, - isSpace = info.isSpace, + isDM = info().isDirect, + isSpace = info().isSpace, trigger = trigger, activeSpace = activeSpace, viaKeyboard = viaKeyboard @@ -27,5 +27,5 @@ fun MatrixRoom.toAnalyticsViewRoom( } private fun MatrixRoom.toActiveSpace(): ViewRoom.ActiveSpace { - return if (info.isPublic) ViewRoom.ActiveSpace.Public else ViewRoom.ActiveSpace.Private + return if (info().isPublic) ViewRoom.ActiveSpace.Public else ViewRoom.ActiveSpace.Private } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index 626824fc298..ec94ec0b92b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -52,8 +52,6 @@ interface MatrixRoom : Closeable { val roomTypingMembersFlow: Flow> val identityStateChangesFlow: Flow> - val info: MatrixRoomInfo get() = roomInfoFlow.value - /** * The current knock requests in the room as a Flow. */ @@ -63,7 +61,7 @@ interface MatrixRoom : Closeable { * A one-to-one is a room with exactly 2 members. * See [the Matrix spec](https://spec.matrix.org/latest/client-server-api/#default-underride-rules). */ - val isOneToOne: Boolean get() = info.activeMembersCount == 2L + val isOneToOne: Boolean get() = info().activeMembersCount == 2L /** * The current loaded members as a StateFlow. @@ -74,6 +72,11 @@ interface MatrixRoom : Closeable { val roomNotificationSettingsStateFlow: StateFlow + /** + * Get the latest room info we have received from the SDK stream. + */ + fun info(): MatrixRoomInfo = roomInfoFlow.value + /** * Try to load the room members and update the membersFlow. */ diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt index 6361f2fd2a4..25adbdf3beb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/alias/MatrixRoomAlias.kt @@ -19,7 +19,7 @@ fun MatrixRoom.matches(roomIdOrAlias: RoomIdOrAlias): Boolean { roomIdOrAlias.roomId == roomId } is RoomIdOrAlias.Alias -> { - roomIdOrAlias.roomAlias == info.canonicalAlias || roomIdOrAlias.roomAlias in info.alternativeAliases + roomIdOrAlias.roomAlias == info().canonicalAlias || roomIdOrAlias.roomAlias in info().alternativeAliases } } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 5892695948c..7d9bbd1bfc8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.test.room +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.ProgressCallback @@ -66,18 +67,9 @@ import java.io.File class FakeMatrixRoom( override val sessionId: SessionId = A_SESSION_ID, override val roomId: RoomId = A_ROOM_ID, - override val displayName: String = "", - override val topic: String? = null, - override val avatarUrl: String? = null, - override var isEncrypted: Boolean = false, - override val canonicalAlias: RoomAlias? = null, - override val alternativeAliases: List = emptyList(), - override val isPublic: Boolean = true, - override val isSpace: Boolean = false, - override val joinedMemberCount: Long = 123L, - override val activeMemberCount: Long = 234L, val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), override val liveTimeline: Timeline = FakeTimeline(), + initialRoomInfo: MatrixRoomInfo = aRoomInfo(), override val roomCoroutineScope: CoroutineScope = TestScope(), private var roomPermalinkResult: () -> Result = { lambdaError() }, private var eventPermalinkResult: (EventId) -> Result = { lambdaError() }, @@ -155,8 +147,8 @@ class FakeMatrixRoom( private val enableEncryptionResult: () -> Result = { lambdaError() }, private val updateJoinRuleResult: (JoinRule) -> Result = { lambdaError() }, ) : MatrixRoom { - private val _roomInfoFlow: MutableSharedFlow = MutableSharedFlow(replay = 1) - override val roomInfoFlow: Flow = _roomInfoFlow + private val _roomInfoFlow: MutableStateFlow = MutableStateFlow(initialRoomInfo) + override val roomInfoFlow: StateFlow = _roomInfoFlow fun givenRoomInfo(roomInfo: MatrixRoomInfo) { _roomInfoFlow.tryEmit(roomInfo) @@ -199,14 +191,14 @@ class FakeMatrixRoom( } override suspend fun updateRoomNotificationSettings(): Result = simulateLongTask { - val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, isEncrypted, isOneToOne).getOrThrow() + val notificationSettings = notificationSettingsService.getRoomNotificationSettings(roomId, info().isEncrypted.orFalse(), isOneToOne).getOrThrow() roomNotificationSettingsStateFlow.value = MatrixRoomNotificationSettingsState.Ready(notificationSettings) return Result.success(Unit) } override suspend fun enableEncryption(): Result = simulateLongTask { enableEncryptionResult().onSuccess { - isEncrypted = true + givenRoomInfo(info().copy(isEncrypted = true)) emitSyncUpdate() } } @@ -616,7 +608,7 @@ class FakeMatrixRoom( } override suspend fun getUpdatedIsEncrypted(): Result = simulateLongTask { - Result.success(isEncrypted) + Result.success(info().isEncrypted.orFalse()) } fun givenRoomMembersState(state: MatrixRoomMembersState) { diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt index 899f2c802d5..eb281f09d61 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembers.kt @@ -40,12 +40,12 @@ fun getRoomMemberAsState(roomMembersState: MatrixRoomMembersState, userId: UserI @Composable fun MatrixRoom.getDirectRoomMember(roomMembersState: MatrixRoomMembersState): State { val roomMembers = roomMembersState.roomMembers() - val roomInfo by roomInfoFlow.collectAsState(null) - return remember(roomMembersState, roomInfo?.isDirect) { + val roomInfo by roomInfoFlow.collectAsState() + return remember(roomMembersState, roomInfo.isDirect) { derivedStateOf { roomMembers ?.filter { it.membership.isActive() } - ?.takeIf { it.size == 2 && roomInfo?.isDirect == true } + ?.takeIf { it.size == 2 && roomInfo.isDirect == true } ?.find { it.userId != sessionId } } } diff --git a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt index d6b3406c7a9..e820004b83c 100644 --- a/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt +++ b/libraries/matrixui/src/main/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomState.kt @@ -105,25 +105,25 @@ fun MatrixRoom.userPowerLevelAsState(updateKey: Long): State { @Composable fun MatrixRoom.isOwnUserAdmin(): Boolean { - val roomInfo by roomInfoFlow.collectAsState(initial = null) - val powerLevel = roomInfo?.userPowerLevels?.get(sessionId) ?: 0L + val roomInfo by roomInfoFlow.collectAsState() + val powerLevel = roomInfo.userPowerLevels[sessionId] ?: 0L return RoomMember.Role.forPowerLevel(powerLevel) == RoomMember.Role.ADMIN } @Composable fun MatrixRoom.rawName(): String? { - val roomInfo by roomInfoFlow.collectAsState(initial = null) - return roomInfo?.rawName + val roomInfo by roomInfoFlow.collectAsState() + return roomInfo.rawName } @Composable fun MatrixRoom.topic(): String? { - val roomInfo by roomInfoFlow.collectAsState(initial = null) - return roomInfo?.topic + val roomInfo by roomInfoFlow.collectAsState() + return roomInfo.topic } @Composable fun MatrixRoom.avatarUrl(): String? { - val roomInfo by roomInfoFlow.collectAsState(initial = null) - return roomInfo?.avatarUrl + val roomInfo by roomInfoFlow.collectAsState() + return roomInfo.avatarUrl } diff --git a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt index 2bc2fee9780..c54795486f0 100644 --- a/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt +++ b/libraries/matrixui/src/test/kotlin/io/element/android/libraries/matrix/ui/room/MatrixRoomMembersTest.kt @@ -32,20 +32,16 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emits other member for encrypted DM with 2 joined members`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - ).apply { - givenRoomInfo(aRoomInfo( + initialRoomInfo = aRoomInfo( isDirect = true, joinedMembersCount = 2, - )) - } + ) + ) moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready(persistentListOf(roomMember1, roomMember2)) ) }.test { - // Skip initial item with outdated info - skipItems(1) - // Check the updated info assertThat(awaitItem().value).isEqualTo(roomMember2) } } @@ -54,6 +50,7 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit null if the room is not a dm`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, + initialRoomInfo = aRoomInfo(isDirect = false) ) moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( @@ -68,20 +65,16 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emits other member even if the room is not encrypted`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - ).apply { - givenRoomInfo(aRoomInfo( + initialRoomInfo = aRoomInfo( isDirect = true, activeMembersCount = 2, - )) - } + ) + ) moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready(persistentListOf(roomMember1, roomMember2)) ) }.test { - // Skip initial item with outdated info - skipItems(1) - // Check the updated info assertThat(awaitItem().value).isEqualTo(roomMember2) } } @@ -90,15 +83,13 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit null if the room has only 1 member`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - ).apply { - givenRoomInfo(aRoomInfo(isDirect = true)) - } + initialRoomInfo = aRoomInfo(isDirect = true) + ) moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready(persistentListOf(roomMember1)) ) }.test { - skipItems(1) assertThat(awaitItem().value).isNull() } } @@ -115,7 +106,6 @@ class MatrixRoomMembersTest { MatrixRoomMembersState.Ready(persistentListOf(roomMember1, roomMember2, roomMember3)) ) }.test { - skipItems(1) assertThat(awaitItem().value).isNull() } } @@ -124,9 +114,8 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit null if the other member is not active`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - ).apply { - givenRoomInfo(aRoomInfo(isDirect = true)) - } + initialRoomInfo = aRoomInfo(isDirect = true), + ) moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready( @@ -137,7 +126,6 @@ class MatrixRoomMembersTest { ) ) }.test { - skipItems(1) assertThat(awaitItem().value).isNull() } } @@ -146,12 +134,11 @@ class MatrixRoomMembersTest { fun `getDirectRoomMember emit the other member if there are 2 active members`() = runTest { val matrixRoom = FakeMatrixRoom( sessionId = A_USER_ID, - ).apply { - givenRoomInfo(aRoomInfo( + initialRoomInfo = aRoomInfo( isDirect = true, activeMembersCount = 2, - )) - } + ) + ) moleculeFlow(RecompositionMode.Immediate) { matrixRoom.getDirectRoomMember( MatrixRoomMembersState.Ready( @@ -163,9 +150,6 @@ class MatrixRoomMembersTest { ) ) }.test { - // Skip initial item with outdated info - skipItems(1) - // Check the updated info assertThat(awaitItem().value).isEqualTo(roomMember2) } } diff --git a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt index ffb633d7073..f79157e8586 100644 --- a/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt +++ b/libraries/mediaviewer/impl/src/main/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenter.kt @@ -64,7 +64,7 @@ class MediaGalleryPresenter @AssistedInject constructor( val coroutineScope = rememberCoroutineScope() var mode by remember { mutableStateOf(MediaGalleryMode.Images) } - val roomInfo by room.roomInfoFlow.collectAsState(null) + val roomInfo by room.roomInfoFlow.collectAsState() var mediaBottomSheetState by remember { mutableStateOf(MediaBottomSheetState.Hidden) } @@ -139,7 +139,7 @@ class MediaGalleryPresenter @AssistedInject constructor( } return MediaGalleryState( - roomName = roomInfo?.name.orEmpty(), + roomName = roomInfo.name.orEmpty(), mode = mode, groupedMediaItems = groupedMediaItems, mediaBottomSheetState = mediaBottomSheetState, diff --git a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt index 81e270a09f3..56396ca2a60 100644 --- a/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt +++ b/libraries/mediaviewer/impl/src/test/kotlin/io/element/android/libraries/mediaviewer/impl/gallery/MediaGalleryPresenterTest.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.mediaviewer.impl.datasource.FakeMediaGalleryDataSource import io.element.android.libraries.mediaviewer.impl.datasource.MediaGalleryDataSource @@ -51,7 +52,7 @@ class MediaGalleryPresenterTest { startLambda = startLambda, ), room = FakeMatrixRoom( - displayName = A_ROOM_NAME, + initialRoomInfo = aRoomInfo(name = A_ROOM_NAME), createTimelineResult = { Result.success(FakeTimeline()) }, ) ) @@ -70,7 +71,7 @@ class MediaGalleryPresenterTest { fun `present - change mode`() = runTest { val presenter = createMediaGalleryPresenter( room = FakeMatrixRoom( - displayName = A_ROOM_NAME, + initialRoomInfo = aRoomInfo(name = A_ROOM_NAME), createTimelineResult = { Result.success(FakeTimeline()) }, ) ) @@ -100,7 +101,7 @@ class MediaGalleryPresenterTest { val presenter = createMediaGalleryPresenter( room = FakeMatrixRoom( sessionId = A_USER_ID, - displayName = A_ROOM_NAME, + initialRoomInfo = aRoomInfo(name = A_ROOM_NAME), createTimelineResult = { Result.success(FakeTimeline()) }, canRedactOwnResult = { Result.success(canDeleteOwn) } ) @@ -143,7 +144,7 @@ class MediaGalleryPresenterTest { val presenter = createMediaGalleryPresenter( room = FakeMatrixRoom( sessionId = A_USER_ID, - displayName = A_ROOM_NAME, + initialRoomInfo = aRoomInfo(name = A_ROOM_NAME), createTimelineResult = { Result.success(FakeTimeline()) }, canRedactOtherResult = { Result.success(canDeleteOther) } ) @@ -176,7 +177,7 @@ class MediaGalleryPresenterTest { fun `present - delete bottom sheet`() = runTest { val presenter = createMediaGalleryPresenter( room = FakeMatrixRoom( - displayName = A_ROOM_NAME, + initialRoomInfo = aRoomInfo(name = A_ROOM_NAME), createTimelineResult = { Result.success(FakeTimeline()) }, ) ) diff --git a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt index da673a2b6ca..aea52c8b782 100644 --- a/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt +++ b/libraries/push/impl/src/main/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandler.kt @@ -152,7 +152,7 @@ class NotificationBroadcastReceiverHandler @Inject constructor( imageUriString = null, imageMimeType = null, threadId = threadId, - roomName = room.info.name, + roomName = room.info().name, roomIsDm = room.isDm(), outGoingMessage = true, ) From 3fc6e3278f579302de728acf39da845c4c5fb3d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 13 Mar 2025 18:49:05 +0100 Subject: [PATCH 09/10] Fix rebase issues --- .../messages/impl/MessagesPresenter.kt | 5 ++- .../messages/impl/MessagesPresenterTest.kt | 9 ++--- .../impl/members/RoomMemberListPresenter.kt | 2 +- .../details/RoomMemberDetailsPresenter.kt | 33 +++++++++++-------- .../details/RoomMemberDetailsPresenterTest.kt | 17 +++++----- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 72fa4d51ec6..b07d98cabac 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -190,9 +190,8 @@ class MessagesPresenter @AssistedInject constructor( val dmRoomMember by room.getDirectRoomMember(membersState) val roomMemberIdentityStateChanges = identityChangeState.roomMemberIdentityStateChanges - // TODO use `RoomInfo.isEncrypted` as a key here once it's available - LifecycleResumeEffect(dmRoomMember, roomMemberIdentityStateChanges) { - if (room.isEncrypted) { + LifecycleResumeEffect(dmRoomMember, roomInfo.isEncrypted) { + if (roomInfo.isEncrypted == true) { val dmRoomMemberId = dmRoomMember?.userId localCoroutineScope.launch { dmRoomMemberId?.let { userId -> diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 3eac4330632..1f7d83401e5 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -1095,28 +1095,25 @@ class MessagesPresenterTest { fun `present - when room is encrypted and a DM, the DM user's identity state is fetched onResume`() = runTest { val room = FakeMatrixRoom( sessionId = A_SESSION_ID, - isEncrypted = true, - isDirect = true, - activeMemberCount = 2L, canUserSendMessageResult = { _, _ -> Result.success(true) }, canRedactOwnResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) }, canUserJoinCallResult = { Result.success(true) }, typingNoticeResult = { Result.success(Unit) }, canUserPinUnpinResult = { Result.success(true) }, + initialRoomInfo = aRoomInfo(isDirect = true, isEncrypted = true) ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(aRoomMember(userId = A_SESSION_ID), aRoomMember(userId = A_USER_ID_2)))) - givenRoomInfo(aRoomInfo(id = roomId, name = "", isDirect = true)) } val encryptionService = FakeEncryptionService(getUserIdentityResult = { Result.success(IdentityState.Verified) }) val presenter = createMessagesPresenter(matrixRoom = room, encryptionService = encryptionService) val lifecycleOwner = FakeLifecycleOwner() presenter.testWithLifecycleOwner(lifecycleOwner) { - skipItems(1) - val initialState = awaitItem() assertThat(initialState.dmUserVerificationState).isNull() + + skipItems(1) ensureAllEventsConsumed() lifecycleOwner.givenState(Lifecycle.State.RESUMED) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 89f82319c58..e154c7ea154 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -183,7 +183,7 @@ class RoomMemberListPresenter @AssistedInject constructor( } private suspend fun RoomMember.withIdentityState(identityStates: ImmutableMap): RoomMemberWithIdentityState { - return if (!room.isEncrypted) { + return if (room.info().isEncrypted != true) { RoomMemberWithIdentityState(this, null) } else { val identityState = identityStates[userId] ?: encryptionService.getUserIdentity(userId).getOrNull() diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index 4e293395bc5..9a5d456cbef 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -27,7 +27,10 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState import io.element.android.libraries.matrix.ui.room.roomMemberIdentityStateChange +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -47,6 +50,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( private val userProfilePresenter = userProfilePresenterFactory.create(roomMemberId) + @OptIn(ExperimentalCoroutinesApi::class) @Composable override fun present(): UserProfileState { val coroutineScope = rememberCoroutineScope() @@ -75,21 +79,22 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( val userProfileState = userProfilePresenter.present() val identityStateChanges by produceState(initialValue = null) { - if (room.isEncrypted) { - // Fetch the initial identity state manually - val identityState = encryptionService.getUserIdentity(roomMemberId).getOrNull() - value = identityState?.let { IdentityStateChange(roomMemberId, it) } + room.roomInfoFlow.filter { it.isEncrypted == true } + .flatMapLatest { + // Fetch the initial identity state manually + val identityState = encryptionService.getUserIdentity(roomMemberId).getOrNull() + value = identityState?.let { IdentityStateChange(roomMemberId, it) } - // Subscribe to the identity changes - room.roomMemberIdentityStateChange() - .map { it.find { it.identityRoomMember.userId == roomMemberId } } - .map { roomMemberIdentityStateChange -> - // If we didn't receive any info, manually fetch it - roomMemberIdentityStateChange?.identityState ?: encryptionService.getUserIdentity(roomMemberId).getOrNull() - } - .filterNotNull() - .collect { value = IdentityStateChange(roomMemberId, it) } - } + // Subscribe to the identity changes + room.roomMemberIdentityStateChange() + .map { it.find { it.identityRoomMember.userId == roomMemberId } } + .map { roomMemberIdentityStateChange -> + // If we didn't receive any info, manually fetch it + roomMemberIdentityStateChange?.identityState ?: encryptionService.getUserIdentity(roomMemberId).getOrNull() + } + .filterNotNull() + } + .collect { value = IdentityStateChange(roomMemberId, it) } } val verificationState = remember(identityStateChanges) { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt index a2c82531062..1506f50f42c 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenterTest.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.lambda.lambdaRecorder @@ -167,10 +168,10 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - when user's identity is verified, the value in the state is VERIFIED`() = runTest { val room = FakeMatrixRoom( - isEncrypted = true, getUpdatedMemberResult = { Result.success(aRoomMember(A_USER_ID)) }, userDisplayNameResult = { Result.success("A custom name") }, userAvatarUrlResult = { Result.success("A custom avatar") }, + initialRoomInfo = aRoomInfo(isEncrypted = true), ) val encryptionService = FakeEncryptionService( getUserIdentityResult = { Result.success(IdentityState.Verified) }, @@ -186,10 +187,10 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - when user's identity is unknown, the value in the state is UNKNOWN`() = runTest { val room = FakeMatrixRoom( - isEncrypted = true, getUpdatedMemberResult = { Result.success(aRoomMember(A_USER_ID)) }, userDisplayNameResult = { Result.success("A custom name") }, userAvatarUrlResult = { Result.success("A custom avatar") }, + initialRoomInfo = aRoomInfo(isEncrypted = true), ) val encryptionService = FakeEncryptionService( getUserIdentityResult = { Result.success(null) }, @@ -205,10 +206,10 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - when user's identity is pinned, the value in the state is UNVERIFIED`() = runTest { val room = FakeMatrixRoom( - isEncrypted = true, getUpdatedMemberResult = { Result.success(aRoomMember(A_USER_ID)) }, userDisplayNameResult = { Result.success("A custom name") }, userAvatarUrlResult = { Result.success("A custom avatar") }, + initialRoomInfo = aRoomInfo(isEncrypted = true), ) val encryptionService = FakeEncryptionService( getUserIdentityResult = { Result.success(IdentityState.Pinned) }, @@ -224,10 +225,10 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - when user's identity is pin violation, the value in the state is UNVERIFIED`() = runTest { val room = FakeMatrixRoom( - isEncrypted = true, getUpdatedMemberResult = { Result.success(aRoomMember(A_USER_ID)) }, userDisplayNameResult = { Result.success("A custom name") }, userAvatarUrlResult = { Result.success("A custom avatar") }, + initialRoomInfo = aRoomInfo(isEncrypted = true), ) val encryptionService = FakeEncryptionService( getUserIdentityResult = { Result.success(IdentityState.PinViolation) }, @@ -243,10 +244,10 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - when user's identity has a verification violation, the value in the state is VERIFICATION_VIOLATION`() = runTest { val room = FakeMatrixRoom( - isEncrypted = true, getUpdatedMemberResult = { Result.success(aRoomMember(A_USER_ID)) }, userDisplayNameResult = { Result.success("A custom name") }, userAvatarUrlResult = { Result.success("A custom avatar") }, + initialRoomInfo = aRoomInfo(isEncrypted = true), ) val encryptionService = FakeEncryptionService( getUserIdentityResult = { Result.success(IdentityState.VerificationViolation) }, @@ -262,10 +263,10 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - user identity updates in real time if the room is encrypted`() = runTest { val room = FakeMatrixRoom( - isEncrypted = true, getUpdatedMemberResult = { Result.success(aRoomMember(A_USER_ID)) }, userDisplayNameResult = { Result.success("A custom name") }, userAvatarUrlResult = { Result.success("A custom avatar") }, + initialRoomInfo = aRoomInfo(isEncrypted = true), ) val encryptionService = FakeEncryptionService( getUserIdentityResult = { Result.success(null) }, @@ -291,10 +292,10 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - user identity can't update in real time if the room is not encrypted`() = runTest { val room = FakeMatrixRoom( - isEncrypted = false, getUpdatedMemberResult = { Result.success(aRoomMember(A_USER_ID)) }, userDisplayNameResult = { Result.success("A custom name") }, userAvatarUrlResult = { Result.success("A custom avatar") }, + initialRoomInfo = aRoomInfo(isEncrypted = false), ) val encryptionService = FakeEncryptionService( getUserIdentityResult = { Result.success(null) }, @@ -315,10 +316,10 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - handles WithdrawVerification action`() = runTest { val room = FakeMatrixRoom( - isEncrypted = true, getUpdatedMemberResult = { Result.success(aRoomMember(A_USER_ID)) }, userDisplayNameResult = { Result.success("A custom name") }, userAvatarUrlResult = { Result.success("A custom avatar") }, + initialRoomInfo = aRoomInfo(isEncrypted = true), ) val withdrawVerificationResult = lambdaRecorder> { Result.success(Unit) } val encryptionService = FakeEncryptionService( From d7ee75189f3221dbf13e24692e606388ffa9795f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 19 Mar 2025 11:56:08 +0100 Subject: [PATCH 10/10] Fetch encryption state when loading a room if it's unknown --- .../android/features/messages/impl/MessagesPresenter.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index b07d98cabac..0ace1a9f47d 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -164,6 +164,11 @@ class MessagesPresenter @AssistedInject constructor( // as those will be handled by the timeline. withContext(dispatchers.io) { room.setUnreadFlag(isUnread = false) + + // If for some reason the encryption state is unknown, fetch it + if (roomInfo.isEncrypted == null) { + room.getUpdatedIsEncrypted() + } } }