From 77a7c0b2e586332fac9f7595abc0414a00da0a0a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Apr 2025 16:50:43 +0200 Subject: [PATCH] Remember flows (#4533) * Add Konsist test to ensure that the result of a function returning a flow is remembered. * Remember flows before they are collected by state. * Fix compilation issue * Make isOnline a val. * Make selectedUsers() a val. * Make flow() a val. * Make getUserConsent(), didAskUserConsent() and getAnalyticsId() some val. * Remove Timeline.paginationStatus() and replace by direct access to the underlined flow. * Simplify test * userConsentFlow must be initialized before because it's used in observeUserConsent * Fix test compilation --- .../appnav/loggedin/LoggedInPresenter.kt | 3 +- .../android/appnav/room/RoomFlowNode.kt | 3 +- .../appnav/room/joined/JoinedRoomFlowNode.kt | 3 +- .../AnalyticsPreferencesPresenter.kt | 3 +- .../impl/AnalyticsOptInPresenterTest.kt | 12 +++--- .../createroom/impl/CreateRoomDataStore.kt | 2 +- .../configureroom/ConfigureRoomPresenter.kt | 4 +- .../impl/root/CreateRoomRootPresenter.kt | 4 +- .../impl/userlist/DefaultUserListPresenter.kt | 2 +- .../impl/userlist/UserListDataStore.kt | 12 +++--- .../features/ftue/impl/FtueFlowNode.kt | 2 +- .../ftue/impl/state/DefaultFtueService.kt | 4 +- .../joinroom/impl/JoinRoomPresenter.kt | 4 +- .../DefaultBiometricAuthenticatorManager.kt | 4 +- .../settings/LockScreenSettingsPresenter.kt | 4 +- .../AccountProviderDataSource.kt | 4 +- .../ConfirmAccountProviderPresenter.kt | 2 +- .../screens/createaccount/MessageParser.kt | 2 +- .../loginpassword/LoginPasswordPresenter.kt | 2 +- .../AccountProviderDataSourceTest.kt | 6 +-- .../messages/impl/MessagesPresenter.kt | 3 +- .../impl/actionlist/ActionListPresenter.kt | 4 +- .../preview/AttachmentsPreviewPresenter.kt | 8 +++- .../MessageComposerPresenter.kt | 24 ++++++------ .../impl/timeline/TimelinePresenter.kt | 12 ++++-- .../protection/TimelineProtectionPresenter.kt | 4 +- .../typing/TypingNotificationPresenter.kt | 4 +- .../migration/impl/MigrationPresenter.kt | 4 +- .../poll/impl/history/PollHistoryPresenter.kt | 2 +- .../advanced/AdvancedSettingsPresenter.kt | 21 +++++------ .../blockedusers/BlockedUsersPresenter.kt | 10 ++--- .../developer/DeveloperSettingsPresenter.kt | 14 ++++--- .../NotificationSettingsPresenter.kt | 8 ++-- .../impl/bugreport/BugReportPresenter.kt | 6 +-- .../DefaultRageshakePreferencesPresenter.kt | 17 +++++---- .../roomdetails/impl/RoomDetailsPresenter.kt | 4 +- .../roomlist/impl/RoomListPresenter.kt | 3 +- .../signedout/impl/SignedOutPresenter.kt | 4 +- .../libraries/matrix/api/sync/SyncService.kt | 5 +-- .../libraries/matrix/api/timeline/Timeline.kt | 7 +++- .../matrix/impl/sync/RustSyncService.kt | 3 ++ .../matrix/impl/timeline/RustTimeline.kt | 20 +++------- .../DefaultCallWidgetSettingsProvider.kt | 2 +- .../matrix/test/sync/FakeSyncService.kt | 3 ++ .../matrix/test/timeline/FakeTimeline.kt | 12 +----- .../impl/DefaultPermissionsPresenter.kt | 13 ++++--- .../analytics/api/AnalyticsService.kt | 12 +++--- .../analytics/impl/DefaultAnalyticsService.kt | 18 +++------ .../impl/DefaultAnalyticsServiceTest.kt | 12 +++--- .../analytics/noop/NoopAnalyticsService.kt | 6 +-- .../analytics/test/FakeAnalyticsService.kt | 9 ++--- .../android/tests/konsist/KonsistFlowTest.kt | 37 +++++++++++++++++++ 52 files changed, 221 insertions(+), 172 deletions(-) create mode 100644 tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index fbecfeb60b..2a7403862e 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -30,7 +30,6 @@ import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.push.api.PushService @@ -79,7 +78,7 @@ class LoggedInPresenter @Inject constructor( .launchIn(this) } val syncIndicator by matrixClient.roomListService.syncIndicator.collectAsState() - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() val showSyncSpinner by remember { derivedStateOf { isOnline && syncIndicator == RoomListService.SyncIndicator.Show diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt index 56f3716ca1..a2016a9171 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/RoomFlowNode.kt @@ -49,7 +49,6 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first @@ -211,7 +210,7 @@ class RoomFlowNode @AssistedInject constructor( } private fun loadingNode(buildContext: BuildContext) = node(buildContext) { modifier -> - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() LoadingRoomNodeView( state = LoadingRoomState.Loading, hasNetworkConnection = isOnline, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt index 49a05c9d36..3c6f30af72 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/room/joined/JoinedRoomFlowNode.kt @@ -36,7 +36,6 @@ import io.element.android.libraries.architecture.inputs import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -114,7 +113,7 @@ class JoinedRoomFlowNode @AssistedInject constructor( private fun loadingNode(buildContext: BuildContext, onBackClick: () -> Unit) = node(buildContext) { modifier -> val loadingRoomState by loadingRoomStateStateFlow.collectAsState() - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() LoadingRoomNodeView( state = loadingRoomState, hasNetworkConnection = isOnline, diff --git a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt index 8212cc6c88..6904734f26 100644 --- a/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt +++ b/features/analytics/impl/src/main/kotlin/io/element/android/features/analytics/impl/preferences/AnalyticsPreferencesPresenter.kt @@ -27,8 +27,7 @@ class AnalyticsPreferencesPresenter @Inject constructor( @Composable override fun present(): AnalyticsPreferencesState { val localCoroutineScope = rememberCoroutineScope() - val isEnabled = analyticsService.getUserConsent() - .collectAsState(initial = false) + val isEnabled = analyticsService.userConsentFlow.collectAsState(initial = false) fun handleEvents(event: AnalyticsOptInEvents) { when (event) { diff --git a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt index 1353d5ab5f..e9319fd7ba 100644 --- a/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt +++ b/features/analytics/impl/src/test/kotlin/io/element/android/features/analytics/impl/AnalyticsOptInPresenterTest.kt @@ -35,10 +35,10 @@ class AnalyticsOptInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(analyticsService.didAskUserConsent().first()).isFalse() + assertThat(analyticsService.didAskUserConsentFlow.first()).isFalse() initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(true)) - assertThat(analyticsService.didAskUserConsent().first()).isTrue() - assertThat(analyticsService.getUserConsent().first()).isTrue() + assertThat(analyticsService.didAskUserConsentFlow.first()).isTrue() + assertThat(analyticsService.userConsentFlow.first()).isTrue() } } @@ -53,10 +53,10 @@ class AnalyticsOptInPresenterTest { presenter.present() }.test { val initialState = awaitItem() - assertThat(analyticsService.didAskUserConsent().first()).isFalse() + assertThat(analyticsService.didAskUserConsentFlow.first()).isFalse() initialState.eventSink.invoke(AnalyticsOptInEvents.EnableAnalytics(false)) - assertThat(analyticsService.didAskUserConsent().first()).isTrue() - assertThat(analyticsService.getUserConsent().first()).isFalse() + assertThat(analyticsService.didAskUserConsentFlow.first()).isTrue() + assertThat(analyticsService.userConsentFlow.first()).isFalse() } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt index 5f629d51eb..29b1525f51 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomDataStore.kt @@ -39,7 +39,7 @@ class CreateRoomDataStore @Inject constructor( } val createRoomConfigWithInvites: Flow = combine( - selectedUserListDataStore.selectedUsers(), + selectedUserListDataStore.selectedUsers, createRoomConfigFlow, ) { selectedUsers, config -> config.copy(invites = selectedUsers.toImmutableList()) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt index fd54b753d9..0025a64df3 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenter.kt @@ -66,7 +66,9 @@ class ConfigureRoomPresenter @Inject constructor( val cameraPermissionState = cameraPermissionPresenter.present() val createRoomConfig by dataStore.createRoomConfigWithInvites.collectAsState(CreateRoomConfig()) val homeserverName = remember { matrixClient.userIdServerName() } - val isKnockFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(initial = false) + val isKnockFeatureEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) + }.collectAsState(initial = false) val roomAddressValidity = remember { mutableStateOf(RoomAddressValidity.Unknown) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt index c05ed770f9..ea8be45e6b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt @@ -52,7 +52,9 @@ class CreateRoomRootPresenter @Inject constructor( val localCoroutineScope = rememberCoroutineScope() val startDmActionState: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } - val isRoomDirectorySearchEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch).collectAsState(initial = false) + val isRoomDirectorySearchEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.RoomDirectorySearch) + }.collectAsState(initial = false) fun handleEvents(event: CreateRoomRootEvents) { when (event) { diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt index ad9b18f624..32d5767cc6 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt @@ -54,7 +54,7 @@ class DefaultUserListPresenter @AssistedInject constructor( recentDirectRooms = matrixClient.getRecentDirectRooms() } var isSearchActive by rememberSaveable { mutableStateOf(false) } - val selectedUsers by userListDataStore.selectedUsers().collectAsState(emptyList()) + val selectedUsers by userListDataStore.selectedUsers.collectAsState(emptyList()) var searchQuery by rememberSaveable { mutableStateOf("") } var searchResults: SearchBarResultState> by remember { mutableStateOf(SearchBarResultState.Initial()) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt index 3be9868c98..a500e3a05f 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListDataStore.kt @@ -8,22 +8,22 @@ package io.element.android.features.createroom.impl.userlist import io.element.android.libraries.matrix.api.user.MatrixUser -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import javax.inject.Inject class UserListDataStore @Inject constructor() { - private val selectedUsers: MutableStateFlow> = MutableStateFlow(emptyList()) + private val _selectedUsers: MutableStateFlow> = MutableStateFlow(emptyList()) fun selectUser(user: MatrixUser) { - if (!selectedUsers.value.contains(user)) { - selectedUsers.tryEmit(selectedUsers.value.plus(user)) + if (!_selectedUsers.value.contains(user)) { + _selectedUsers.tryEmit(_selectedUsers.value.plus(user)) } } fun removeUserFromSelection(user: MatrixUser) { - selectedUsers.tryEmit(selectedUsers.value.minus(user)) + _selectedUsers.tryEmit(_selectedUsers.value.minus(user)) } - fun selectedUsers(): Flow> = selectedUsers + val selectedUsers = _selectedUsers.asStateFlow() } diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 034701fec4..71a27dcaae 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -84,7 +84,7 @@ class FtueFlowNode @AssistedInject constructor( moveToNextStepIfNeeded() }) - analyticsService.didAskUserConsent() + analyticsService.didAskUserConsentFlow .distinctUntilChanged() .onEach { moveToNextStepIfNeeded() } .launchIn(lifecycleScope) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt index 0409d73538..744053b976 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/state/DefaultFtueService.kt @@ -66,7 +66,7 @@ class DefaultFtueService @Inject constructor( .onEach { updateState() } .launchIn(sessionCoroutineScope) - analyticsService.didAskUserConsent() + analyticsService.didAskUserConsentFlow .distinctUntilChanged() .onEach { updateState() } .launchIn(sessionCoroutineScope) @@ -118,7 +118,7 @@ class DefaultFtueService @Inject constructor( } private suspend fun needsAnalyticsOptIn(): Boolean { - return analyticsService.didAskUserConsent().first().not() + return analyticsService.didAskUserConsentFlow.first().not() } private suspend fun shouldAskNotificationPermissions(): Boolean { diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index 09ec3c9a5f..b1fb345a0c 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -82,7 +82,9 @@ class JoinRoomPresenter @AssistedInject constructor( override fun present(): JoinRoomState { val coroutineScope = rememberCoroutineScope() var retryCount by remember { mutableIntStateOf(0) } - val roomInfo by matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()).collectAsState(initial = Optional.empty()) + val roomInfo by remember { + matrixClient.getRoomInfoFlow(roomId.toRoomIdOrAlias()) + }.collectAsState(initial = Optional.empty()) val joinAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val knockAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val cancelKnockAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt index 62de1f902d..5c2b1fdd53 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/biometric/DefaultBiometricAuthenticatorManager.kt @@ -87,7 +87,9 @@ class DefaultBiometricAuthenticatorManager @Inject constructor( @Composable override fun rememberUnlockBiometricAuthenticator(): BiometricAuthenticator { - val isBiometricAllowed by lockScreenStore.isBiometricUnlockAllowed().collectAsState(initial = false) + val isBiometricAllowed by remember { + lockScreenStore.isBiometricUnlockAllowed() + }.collectAsState(initial = false) val lifecycleState by LocalLifecycleOwner.current.lifecycle.currentStateFlow.collectAsState() val isAvailable by remember(lifecycleState) { derivedStateOf { isBiometricAllowed && hasAvailableAuthenticator } diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt index f9a2cd8767..97dd875b01 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsPresenter.kt @@ -38,7 +38,9 @@ class LockScreenSettingsPresenter @Inject constructor( value = !lockScreenConfig.isPinMandatory && hasPinCode } } - val isBiometricEnabled by lockScreenStore.isBiometricUnlockAllowed().collectAsState(initial = false) + val isBiometricEnabled by remember { + lockScreenStore.isBiometricUnlockAllowed() + }.collectAsState(initial = false) var showRemovePinConfirmation by remember { mutableStateOf(false) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt index 3f9c368a1d..3e1fc1853c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSource.kt @@ -33,9 +33,7 @@ class AccountProviderDataSource @Inject constructor( defaultAccountProvider ) - fun flow(): StateFlow { - return accountProvider.asStateFlow() - } + val flow: StateFlow = accountProvider.asStateFlow() fun reset() { accountProvider.tryEmit(defaultAccountProvider) 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 eb5a8fee67..99b029a928 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 @@ -52,7 +52,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor( @Composable override fun present(): ConfirmAccountProviderState { - val accountProvider by accountProviderDataSource.flow().collectAsState() + val accountProvider by accountProviderDataSource.flow.collectAsState() val localCoroutineScope = rememberCoroutineScope() val loginFlowAction: MutableState> = remember { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt index 027036059d..c937cf9d48 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt @@ -30,7 +30,7 @@ class DefaultMessageParser @Inject constructor( val parser = Json { ignoreUnknownKeys = true } val response = parser.decodeFromString(MobileRegistrationResponse.serializer(), message) val userId = response.userId ?: error("No user ID in response") - val homeServer = response.homeServer ?: accountProviderDataSource.flow().value.url + val homeServer = response.homeServer ?: accountProviderDataSource.flow.value.url val accessToken = response.accessToken ?: error("No access token in response") val deviceId = response.deviceId ?: error("No device ID in response") return ExternalSession( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt index ea60dc66f0..3a12715f6e 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/loginpassword/LoginPasswordPresenter.kt @@ -40,7 +40,7 @@ class LoginPasswordPresenter @Inject constructor( val formState = rememberSaveable { mutableStateOf(LoginFormState.Default) } - val accountProvider by accountProviderDataSource.flow().collectAsState() + val accountProvider by accountProviderDataSource.flow.collectAsState() fun handleEvents(event: LoginPasswordEvents) { when (event) { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt index b0570cee93..e9a8595f3b 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderDataSourceTest.kt @@ -23,7 +23,7 @@ class AccountProviderDataSourceTest { @Test fun `present - initial state`() = runTest { val sut = AccountProviderDataSource(FakeEnterpriseService()) - sut.flow().test { + sut.flow.test { val initialState = awaitItem() assertThat(initialState).isEqualTo( AccountProvider( @@ -43,7 +43,7 @@ class AccountProviderDataSourceTest { val sut = AccountProviderDataSource(FakeEnterpriseService( defaultHomeserverResult = { AuthenticationConfig.MATRIX_ORG_URL } )) - sut.flow().test { + sut.flow.test { val initialState = awaitItem() assertThat(initialState).isEqualTo( AccountProvider( @@ -61,7 +61,7 @@ class AccountProviderDataSourceTest { @Test fun `present - user change and reset`() = runTest { val sut = AccountProviderDataSource(FakeEnterpriseService()) - sut.flow().test { + sut.flow.test { val initialState = awaitItem() assertThat(initialState.url).isEqualTo(FakeEnterpriseService.A_FAKE_HOMESERVER) sut.userSelection(AccountProvider(url = "https://example.com")) 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 4541e267fc..b5a1842212 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 @@ -75,7 +75,6 @@ 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.powerlevels.canSendMessage import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.ui.messages.reply.map import io.element.android.libraries.matrix.ui.model.getAvatarData @@ -183,7 +182,7 @@ class MessagesPresenter @AssistedInject constructor( showReinvitePrompt = !hasDismissedInviteDialog && composerHasFocus && roomInfo.isDm && roomInfo.activeMembersCount == 1L } } - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 5274dcbc22..2ca03e09ea 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -84,7 +84,9 @@ class DefaultActionListPresenter @AssistedInject constructor( mutableStateOf(ActionListState.Target.None) } - val isDeveloperModeEnabled by appPreferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false) + val isDeveloperModeEnabled by remember { + appPreferencesStore.isDeveloperModeEnabledFlow() + }.collectAsState(initial = false) val isPinnedEventsEnabled = isPinnedMessagesFeatureEnabled() val pinnedEventIds by remember { room.roomInfoFlow.map { it.pinnedEventIds } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt index e7160d26dc..2f560cd7ac 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/attachments/preview/AttachmentsPreviewPresenter.kt @@ -78,8 +78,12 @@ class AttachmentsPreviewPresenter @AssistedInject constructor( val ongoingSendAttachmentJob = remember { mutableStateOf(null) } - val allowCaption by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionCreation).collectAsState(initial = false) - val showCaptionCompatibilityWarning by featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionWarning).collectAsState(initial = false) + val allowCaption by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionCreation) + }.collectAsState(initial = false) + val showCaptionCompatibilityWarning by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionWarning) + }.collectAsState(initial = false) var useSendQueue by remember { mutableStateOf(false) } var preprocessMediaJob by remember { mutableStateOf(null) } 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 73057b1a51..95ea275278 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 @@ -177,7 +177,9 @@ class MessageComposerPresenter @AssistedInject constructor( } var showAttachmentSourcePicker: Boolean by remember { mutableStateOf(false) } - val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true) + val sendTypingNotifications by remember { + sessionPreferencesStore.isSendTypingNotificationsEnabled() + }.collectAsState(initial = true) LaunchedEffect(cameraPermissionState.permissionGranted) { if (cameraPermissionState.permissionGranted) { @@ -397,16 +399,16 @@ class MessageComposerPresenter @AssistedInject constructor( .stateIn(this, SharingStarted.Lazily, emptyList()) combine(mentionTriggerFlow, room.membersStateFlow, roomAliasSuggestionsFlow) { suggestion, roomMembersState, roomAliasSuggestions -> - val result = suggestionsProcessor.process( - suggestion = suggestion, - roomMembersState = roomMembersState, - roomAliasSuggestions = roomAliasSuggestions, - currentUserId = currentUserId, - canSendRoomMention = ::canSendRoomMention, - ) - suggestions.clear() - suggestions.addAll(result) - } + val result = suggestionsProcessor.process( + suggestion = suggestion, + roomMembersState = roomMembersState, + roomAliasSuggestions = roomAliasSuggestions, + currentUserId = currentUserId, + canSendRoomMention = ::canSendRoomMention, + ) + suggestions.clear() + suggestions.addAll(result) + } .collect() } } 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 4061be78c1..6ac01c990b 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 @@ -109,9 +109,15 @@ class TimelinePresenter @AssistedInject constructor( val messageShield: MutableState = remember { mutableStateOf(null) } val resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailurePresenter.present() - val isSendPublicReadReceiptsEnabled by sessionPreferencesStore.isSendPublicReadReceiptsEnabled().collectAsState(initial = true) - val renderReadReceipts by sessionPreferencesStore.isRenderReadReceiptsEnabled().collectAsState(initial = true) - val isLive by timelineController.isLive().collectAsState(initial = true) + val isSendPublicReadReceiptsEnabled by remember { + sessionPreferencesStore.isSendPublicReadReceiptsEnabled() + }.collectAsState(initial = true) + val renderReadReceipts by remember { + sessionPreferencesStore.isRenderReadReceiptsEnabled() + }.collectAsState(initial = true) + val isLive by remember { + timelineController.isLive() + }.collectAsState(initial = true) fun handleEvents(event: TimelineEvents) { when (event) { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt index ce2ef75321..e4283b1a02 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/protection/TimelineProtectionPresenter.kt @@ -25,7 +25,9 @@ class TimelineProtectionPresenter @Inject constructor( ) : Presenter { @Composable override fun present(): TimelineProtectionState { - val hideMediaContent by appPreferencesStore.doesHideImagesAndVideosFlow().collectAsState(initial = false) + val hideMediaContent by remember { + appPreferencesStore.doesHideImagesAndVideosFlow() + }.collectAsState(initial = false) var allowedEvents by remember { mutableStateOf>(setOf()) } val protectionState by remember(hideMediaContent) { derivedStateOf { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt index 35759ade74..4dfc02a2a1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/TypingNotificationPresenter.kt @@ -37,7 +37,9 @@ class TypingNotificationPresenter @Inject constructor( ) : Presenter { @Composable override fun present(): TypingNotificationState { - val renderTypingNotifications by sessionPreferencesStore.isRenderTypingNotificationsEnabled().collectAsState(initial = true) + val renderTypingNotifications by remember { + sessionPreferencesStore.isRenderTypingNotificationsEnabled() + }.collectAsState(initial = true) val typingMembersState by produceState(initialValue = persistentListOf(), key1 = renderTypingNotifications) { if (renderTypingNotifications) { observeRoomTypingMembers() diff --git a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt index f89d81685f..3d8fc3a0aa 100644 --- a/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt +++ b/features/migration/impl/src/main/kotlin/io/element/android/features/migration/impl/MigrationPresenter.kt @@ -33,7 +33,9 @@ class MigrationPresenter @Inject constructor( @Composable override fun present(): MigrationState { - val migrationStoreVersion by migrationStore.applicationMigrationVersion().collectAsState(initial = null) + val migrationStoreVersion by remember { + migrationStore.applicationMigrationVersion() + }.collectAsState(initial = null) var migrationAction: AsyncData by remember { mutableStateOf(AsyncData.Uninitialized) } // Uncomment this block to run the migration everytime diff --git a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt index 00f68e923b..d4da6a76b4 100644 --- a/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt +++ b/features/poll/impl/src/main/kotlin/io/element/android/features/poll/impl/history/PollHistoryPresenter.kt @@ -40,7 +40,7 @@ class PollHistoryPresenter @Inject constructor( @Composable override fun present(): PollHistoryState { val timeline = room.liveTimeline - val paginationState by timeline.paginationStatus(Timeline.PaginationDirection.BACKWARDS).collectAsState() + val paginationState by timeline.backwardPaginationStatus.collectAsState() val pollHistoryItemsFlow = remember { timeline.timelineItems.map { items -> pollHistoryItemFactory.create(items) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt index b256997d1c..80f9a98b0c 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/advanced/AdvancedSettingsPresenter.kt @@ -29,19 +29,18 @@ class AdvancedSettingsPresenter @Inject constructor( @Composable override fun present(): AdvancedSettingsState { val localCoroutineScope = rememberCoroutineScope() - val isDeveloperModeEnabled by appPreferencesStore - .isDeveloperModeEnabledFlow() - .collectAsState(initial = false) - val isSharePresenceEnabled by sessionPreferencesStore - .isSharePresenceEnabled() - .collectAsState(initial = true) - val doesCompressMedia by sessionPreferencesStore - .doesCompressMedia() - .collectAsState(initial = true) + val isDeveloperModeEnabled by remember { + appPreferencesStore.isDeveloperModeEnabledFlow() + }.collectAsState(initial = false) + val isSharePresenceEnabled by remember { + sessionPreferencesStore.isSharePresenceEnabled() + }.collectAsState(initial = true) + val doesCompressMedia by remember { + sessionPreferencesStore.doesCompressMedia() + }.collectAsState(initial = true) val theme by remember { appPreferencesStore.getThemeFlow().mapToTheme() - } - .collectAsState(initial = Theme.System) + }.collectAsState(initial = Theme.System) var showChangeThemeDialog by remember { mutableStateOf(false) } fun handleEvents(event: AdvancedSettingsEvents) { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt index 19365e307c..c673276a17 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/blockedusers/BlockedUsersPresenter.kt @@ -44,17 +44,17 @@ class BlockedUsersPresenter @Inject constructor( mutableStateOf(AsyncAction.Uninitialized) } - val renderBlockedUsersDetail = featureFlagService - .isFeatureEnabledFlow(FeatureFlags.ShowBlockedUsersDetails) - .collectAsState(initial = false) + val renderBlockedUsersDetail by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.ShowBlockedUsersDetails) + }.collectAsState(initial = false) val ignoredUserIds by matrixClient.ignoredUsersFlow.collectAsState() val ignoredMatrixUser by produceState( initialValue = ignoredUserIds.map { MatrixUser(userId = it) }, - key1 = renderBlockedUsersDetail.value, + key1 = renderBlockedUsersDetail, key2 = ignoredUserIds ) { value = ignoredUserIds.map { - if (renderBlockedUsersDetail.value) { + if (renderBlockedUsersDetail) { matrixClient.getProfile(it).getOrNull() } else { null diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt index aa2c5a1c50..5159983a6e 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenter.kt @@ -71,12 +71,14 @@ class DeveloperSettingsPresenter @Inject constructor( val clearCacheAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } - val customElementCallBaseUrl by appPreferencesStore - .getCustomElementCallBaseUrlFlow() - .collectAsState(initial = null) - val hideImagesAndVideos by appPreferencesStore - .doesHideImagesAndVideosFlow() - .collectAsState(initial = false) + val customElementCallBaseUrl by remember { + appPreferencesStore + .getCustomElementCallBaseUrlFlow() + }.collectAsState(initial = null) + val hideImagesAndVideos by remember { + appPreferencesStore + .doesHideImagesAndVideosFlow() + }.collectAsState(initial = false) val tracingLogLevelFlow = remember { appPreferencesStore.getTracingLogLevelFlow().map { AsyncData.Success(it.toLogLevelItem()) } diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 317c6e796e..3c1c81e758 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -58,9 +58,9 @@ class NotificationSettingsPresenter @Inject constructor( val changeNotificationSettingAction: MutableState> = remember { mutableStateOf(AsyncAction.Uninitialized) } val localCoroutineScope = rememberCoroutineScope() - val appNotificationsEnabled = userPushStore - .getNotificationEnabledForDevice() - .collectAsState(initial = false) + val appNotificationsEnabled by remember { + userPushStore.getNotificationEnabledForDevice() + }.collectAsState(initial = false) val matrixSettings: MutableState = remember { mutableStateOf(NotificationSettingsState.MatrixSettings.Uninitialized) @@ -158,7 +158,7 @@ class NotificationSettingsPresenter @Inject constructor( matrixSettings = matrixSettings.value, appSettings = NotificationSettingsState.AppSettings( systemNotificationsEnabled = systemNotificationsEnabled.value, - appNotificationsEnabled = appNotificationsEnabled.value + appNotificationsEnabled = appNotificationsEnabled, ), changeNotificationSettingAction = changeNotificationSettingAction.value, currentPushDistributor = currentDistributor, diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt index 294fc194d5..455cefdb24 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenter.kt @@ -64,9 +64,9 @@ class BugReportPresenter @Inject constructor( screenshotHolder.getFileUri() ) } - val crashInfo: String by crashDataStore - .crashInfo() - .collectAsState(initial = "") + val crashInfo: String by remember { + crashDataStore.crashInfo() + }.collectAsState(initial = "") val sendingProgress = remember { mutableFloatStateOf(0f) diff --git a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt index ba883b50a9..b4f45a3bdc 100644 --- a/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt +++ b/features/rageshake/impl/src/main/kotlin/io/element/android/features/rageshake/impl/preferences/DefaultRageshakePreferencesPresenter.kt @@ -10,6 +10,7 @@ package io.element.android.features.rageshake.impl.preferences import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -39,13 +40,13 @@ class DefaultRageshakePreferencesPresenter @Inject constructor( mutableStateOf(rageshake.isAvailable()) } val isFeatureAvailable = remember { rageshakeFeatureAvailability.isAvailable() } - val isEnabled = rageshakeDataStore - .isEnabled() - .collectAsState(initial = false) + val isEnabled by remember { + rageshakeDataStore.isEnabled() + }.collectAsState(initial = false) - val sensitivity = rageshakeDataStore - .sensitivity() - .collectAsState(initial = 0f) + val sensitivity by remember { + rageshakeDataStore.sensitivity() + }.collectAsState(initial = 0f) fun handleEvents(event: RageshakePreferencesEvents) { when (event) { @@ -56,9 +57,9 @@ class DefaultRageshakePreferencesPresenter @Inject constructor( return RageshakePreferencesState( isFeatureEnabled = isFeatureAvailable, - isEnabled = isEnabled.value, + isEnabled = isEnabled, isSupported = isSupported.value, - sensitivity = sensitivity.value, + sensitivity = sensitivity, eventSink = ::handleEvents ) } 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 df1daf6386..bcbdb83e2d 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 @@ -122,7 +122,9 @@ class RoomDetailsPresenter @Inject constructor( } val canHandleKnockRequests by room.canHandleKnockRequestsAsState(syncUpdateFlow.value) - val isKnockRequestsEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock).collectAsState(false) + val isKnockRequestsEnabled by remember { + featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock) + }.collectAsState(false) val knockRequestsCount by produceState(null) { room.knockRequestsFlow.collect { value = it.size } } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 19e46607ef..660f0f516f 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt @@ -50,7 +50,6 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.RecoveryState import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.sync.SyncService -import io.element.android.libraries.matrix.api.sync.isOnline import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore @@ -101,7 +100,7 @@ class RoomListPresenter @Inject constructor( val coroutineScope = rememberCoroutineScope() val leaveRoomState = leaveRoomPresenter.present() val matrixUser = client.userProfile.collectAsState() - val isOnline by syncService.isOnline().collectAsState() + val isOnline by syncService.isOnline.collectAsState() val filtersState = filtersPresenter.present() val searchState = searchPresenter.present() val acceptDeclineInviteState = acceptDeclineInvitePresenter.present() diff --git a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt index dd7c24ab35..773954c3e0 100644 --- a/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt +++ b/features/signedout/impl/src/main/kotlin/io/element/android/features/signedout/impl/SignedOutPresenter.kt @@ -34,7 +34,9 @@ class SignedOutPresenter @AssistedInject constructor( @Composable override fun present(): SignedOutState { - val sessions by sessionStore.sessionsFlow().collectAsState(initial = emptyList()) + val sessions by remember { + sessionStore.sessionsFlow() + }.collectAsState(initial = emptyList()) val signedOutSession by remember { derivedStateOf { sessions.firstOrNull { it.userId == sessionId } } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt index 21554dbbd6..13cb54b500 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/sync/SyncService.kt @@ -7,7 +7,6 @@ package io.element.android.libraries.matrix.api.sync -import io.element.android.libraries.core.coroutine.mapState import kotlinx.coroutines.flow.StateFlow interface SyncService { @@ -25,6 +24,6 @@ interface SyncService { * Flow of [SyncState]. Will be updated as soon as the current [SyncState] changes. */ val syncState: StateFlow -} -fun SyncService.isOnline(): StateFlow = syncState.mapState { it != SyncState.Offline } + val isOnline: StateFlow +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt index c74f7b2c00..904aae0355 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/Timeline.kt @@ -49,7 +49,10 @@ interface Timeline : AutoCloseable { val membershipChangeEventReceived: Flow suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result suspend fun paginate(direction: PaginationDirection): Result - fun paginationStatus(direction: PaginationDirection): StateFlow + + val backwardPaginationStatus: StateFlow + val forwardPaginationStatus: StateFlow + val timelineItems: Flow> suspend fun sendMessage( @@ -105,7 +108,7 @@ interface Timeline : AutoCloseable { caption: String?, formattedCaption: String?, progressCallback: ProgressCallback?, - ): Result + ): Result suspend fun sendFile( file: File, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt index 3318e1384f..1d8eb7c0ea 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/sync/RustSyncService.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.impl.sync +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import kotlinx.coroutines.CoroutineDispatcher @@ -73,4 +74,6 @@ class RustSyncService( } .distinctUntilChanged() .stateIn(sessionCoroutineScope, SharingStarted.Eagerly, SyncState.Idle) + + override val isOnline: StateFlow = syncState.mapState { it != SyncState.Offline } } 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 46c2bfebd4..efe16f0073 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 @@ -54,7 +54,6 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.Flow 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 @@ -127,11 +126,11 @@ class RustTimeline( private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(mode) private val typingNotificationPostProcessor = TypingNotificationPostProcessor(mode) - private val backPaginationStatus = MutableStateFlow( + override val backwardPaginationStatus = MutableStateFlow( Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PINNED_EVENTS) ) - private val forwardPaginationStatus = MutableStateFlow( + override val forwardPaginationStatus = MutableStateFlow( Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode == Timeline.Mode.FOCUSED_ON_EVENT) ) @@ -167,7 +166,7 @@ class RustTimeline( private fun updatePaginationStatus(direction: Timeline.PaginationDirection, update: (Timeline.PaginationStatus) -> Timeline.PaginationStatus) { when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backPaginationStatus.getAndUpdate(update) + Timeline.PaginationDirection.BACKWARDS -> backwardPaginationStatus.getAndUpdate(update) Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus.getAndUpdate(update) } } @@ -185,7 +184,7 @@ class RustTimeline( } }.onFailure { error -> if (error is TimelineException.CannotPaginate) { - Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backPaginationStatus.value}") + Timber.d("Can't paginate $direction on room ${matrixRoom.roomId} with paginationStatus: ${backwardPaginationStatus.value}") } else { updatePaginationStatus(direction) { it.copy(isPaginating = false) } Timber.e(error, "Error paginating $direction on room ${matrixRoom.roomId}") @@ -199,21 +198,14 @@ class RustTimeline( private fun canPaginate(direction: Timeline.PaginationDirection): Boolean { if (!isTimelineInitialized.value) return false return when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backPaginationStatus.value.canPaginate + Timeline.PaginationDirection.BACKWARDS -> backwardPaginationStatus.value.canPaginate Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus.value.canPaginate } } - override fun paginationStatus(direction: Timeline.PaginationDirection): StateFlow { - return when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backPaginationStatus - Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus - } - } - override val timelineItems: Flow> = combine( _timelineItems, - backPaginationStatus, + backwardPaginationStatus, forwardPaginationStatus, matrixRoom.roomInfoFlow.map { it.creator to it.isDm }.distinctUntilChanged(), isTimelineInitialized, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt index b761dd7e3b..7f179ad2f7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/widget/DefaultCallWidgetSettingsProvider.kt @@ -29,7 +29,7 @@ class DefaultCallWidgetSettingsProvider @Inject constructor( private val analyticsService: AnalyticsService, ) : CallWidgetSettingsProvider { override suspend fun provide(baseUrl: String, widgetId: String, encrypted: Boolean): MatrixWidgetSettings { - val isAnalyticsEnabled = analyticsService.getUserConsent().first() + val isAnalyticsEnabled = analyticsService.userConsentFlow.first() val options = VirtualElementCallWidgetOptions( elementCallUrl = baseUrl, widgetId = widgetId, diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt index 9019eea646..5469e5b4aa 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/sync/FakeSyncService.kt @@ -7,6 +7,7 @@ package io.element.android.libraries.matrix.test.sync +import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncState import kotlinx.coroutines.flow.MutableStateFlow @@ -29,6 +30,8 @@ class FakeSyncService( override val syncState: StateFlow = syncStateFlow + override val isOnline: StateFlow = syncState.mapState { it != SyncState.Offline } + suspend fun emitSyncState(syncState: SyncState) { syncStateFlow.emit(syncState) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt index 1c335057b5..f43c00c63a 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeTimeline.kt @@ -28,19 +28,18 @@ import io.element.android.tests.testutils.lambda.lambdaError import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import java.io.File class FakeTimeline( private val name: String = "FakeTimeline", override val timelineItems: Flow> = MutableStateFlow(emptyList()), - private val backwardPaginationStatus: MutableStateFlow = MutableStateFlow( + override val backwardPaginationStatus: MutableStateFlow = MutableStateFlow( Timeline.PaginationStatus( isPaginating = false, hasMoreToLoad = true ) ), - private val forwardPaginationStatus: MutableStateFlow = MutableStateFlow( + override val forwardPaginationStatus: MutableStateFlow = MutableStateFlow( Timeline.PaginationStatus( isPaginating = false, hasMoreToLoad = false @@ -377,13 +376,6 @@ class FakeTimeline( override suspend fun paginate(direction: Timeline.PaginationDirection): Result = paginateLambda(direction) - override fun paginationStatus(direction: Timeline.PaginationDirection): StateFlow { - return when (direction) { - Timeline.PaginationDirection.BACKWARDS -> backwardPaginationStatus - Timeline.PaginationDirection.FORWARDS -> forwardPaginationStatus - } - } - var loadReplyDetailsLambda: (eventId: EventId) -> InReplyTo = { InReplyTo.NotLoaded(it) } diff --git a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt index 3e5e3b7407..e9072e3666 100644 --- a/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt +++ b/libraries/permissions/impl/src/main/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenter.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import com.google.accompanist.permissions.ExperimentalPermissionsApi @@ -56,13 +57,13 @@ class DefaultPermissionsPresenter @AssistedInject constructor( // To reset the store: ResetStore() - val isAlreadyDenied: Boolean by permissionsStore - .isPermissionDenied(permission) - .collectAsState(initial = false) + val isAlreadyDenied: Boolean by remember { + permissionsStore.isPermissionDenied(permission) + }.collectAsState(initial = false) - val isAlreadyAsked: Boolean by permissionsStore - .isPermissionAsked(permission) - .collectAsState(initial = false) + val isAlreadyAsked: Boolean by remember { + permissionsStore.isPermissionAsked(permission) + }.collectAsState(initial = false) var permissionState: PermissionState? = null diff --git a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt index 9ff4053b33..750a6d1d17 100644 --- a/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt +++ b/services/analytics/api/src/main/kotlin/io/element/android/services/analytics/api/AnalyticsService.kt @@ -19,9 +19,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker { fun getAvailableAnalyticsProviders(): Set /** - * Return a Flow of Boolean, true if the user has given their consent. + * A Flow of Boolean, true if the user has given their consent. */ - fun getUserConsent(): Flow + val userConsentFlow: Flow /** * Update the user consent value. @@ -29,9 +29,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker { suspend fun setUserConsent(userConsent: Boolean) /** - * Return a Flow of Boolean, true if the user has been asked for their consent. + * A Flow of Boolean, true if the user has been asked for their consent. */ - fun didAskUserConsent(): Flow + val didAskUserConsentFlow: Flow /** * Store the fact that the user has been asked for their consent. @@ -39,9 +39,9 @@ interface AnalyticsService : AnalyticsTracker, ErrorTracker { suspend fun setDidAskUserConsent() /** - * Return a Flow of String, used for analytics Id. + * A Flow of String, used for analytics Id. */ - fun getAnalyticsId(): Flow + val analyticsIdFlow: Flow /** * Update analyticsId from the AccountData. diff --git a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt index 6dafd7f334..5ee74860aa 100644 --- a/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt +++ b/services/analytics/impl/src/main/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsService.kt @@ -43,6 +43,10 @@ class DefaultAnalyticsService @Inject constructor( // Cache for the properties to send private var pendingUserProperties: UserProperties? = null + override val userConsentFlow: Flow = analyticsStore.userConsentFlow + override val didAskUserConsentFlow: Flow = analyticsStore.didAskUserConsentFlow + override val analyticsIdFlow: Flow = analyticsStore.analyticsIdFlow + init { observeUserConsent() observeSessions() @@ -52,19 +56,11 @@ class DefaultAnalyticsService @Inject constructor( return analyticsProviders } - override fun getUserConsent(): Flow { - return analyticsStore.userConsentFlow - } - override suspend fun setUserConsent(userConsent: Boolean) { Timber.tag(analyticsTag.value).d("setUserConsent($userConsent)") analyticsStore.setUserConsent(userConsent) } - override fun didAskUserConsent(): Flow { - return analyticsStore.didAskUserConsentFlow - } - override suspend fun setDidAskUserConsent() { Timber.tag(analyticsTag.value).d("setDidAskUserConsent()") analyticsStore.setDidAskUserConsent() @@ -74,10 +70,6 @@ class DefaultAnalyticsService @Inject constructor( analyticsStore.setDidAskUserConsent(false) } - override fun getAnalyticsId(): Flow { - return analyticsStore.analyticsIdFlow - } - override suspend fun setAnalyticsId(analyticsId: String) { Timber.tag(analyticsTag.value).d("setAnalyticsId($analyticsId)") analyticsStore.setAnalyticsId(analyticsId) @@ -93,7 +85,7 @@ class DefaultAnalyticsService @Inject constructor( } private fun observeUserConsent() { - getUserConsent() + userConsentFlow .onEach { consent -> Timber.tag(analyticsTag.value).d("User consent updated to $consent") userConsent.set(consent) diff --git a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt index 1b00b238eb..e05a6e4208 100644 --- a/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt +++ b/services/analytics/impl/src/test/kotlin/io/element/android/services/analytics/impl/DefaultAnalyticsServiceTest.kt @@ -132,10 +132,10 @@ class DefaultAnalyticsServiceTest { analyticsStore = store, ) assertThat(store.userConsentFlow.first()).isFalse() - assertThat(sut.getUserConsent().first()).isFalse() + assertThat(sut.userConsentFlow.first()).isFalse() sut.setUserConsent(true) assertThat(store.userConsentFlow.first()).isTrue() - assertThat(sut.getUserConsent().first()).isTrue() + assertThat(sut.userConsentFlow.first()).isTrue() } @Test @@ -146,10 +146,10 @@ class DefaultAnalyticsServiceTest { analyticsStore = store, ) assertThat(store.analyticsIdFlow.first()).isEqualTo("") - assertThat(sut.getAnalyticsId().first()).isEqualTo("") + assertThat(sut.analyticsIdFlow.first()).isEqualTo("") sut.setAnalyticsId(AN_ID) assertThat(store.analyticsIdFlow.first()).isEqualTo(AN_ID) - assertThat(sut.getAnalyticsId().first()).isEqualTo(AN_ID) + assertThat(sut.analyticsIdFlow.first()).isEqualTo(AN_ID) } @Test @@ -160,10 +160,10 @@ class DefaultAnalyticsServiceTest { analyticsStore = store, ) assertThat(store.didAskUserConsentFlow.first()).isFalse() - assertThat(sut.didAskUserConsent().first()).isFalse() + assertThat(sut.didAskUserConsentFlow.first()).isFalse() sut.setDidAskUserConsent() assertThat(store.didAskUserConsentFlow.first()).isTrue() - assertThat(sut.didAskUserConsent().first()).isTrue() + assertThat(sut.didAskUserConsentFlow.first()).isTrue() } @Test diff --git a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt index eb05c5c151..9af15543f0 100644 --- a/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt +++ b/services/analytics/noop/src/main/kotlin/io/element/android/services/analytics/noop/NoopAnalyticsService.kt @@ -24,11 +24,11 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class NoopAnalyticsService @Inject constructor() : AnalyticsService { override fun getAvailableAnalyticsProviders(): Set = emptySet() - override fun getUserConsent(): Flow = flowOf(false) + override val userConsentFlow: Flow = flowOf(false) override suspend fun setUserConsent(userConsent: Boolean) = Unit - override fun didAskUserConsent(): Flow = flowOf(true) + override val didAskUserConsentFlow: Flow = flowOf(true) override suspend fun setDidAskUserConsent() = Unit - override fun getAnalyticsId(): Flow = flowOf("") + override val analyticsIdFlow: Flow = flowOf("") override suspend fun setAnalyticsId(analyticsId: String) = Unit override suspend fun reset() = Unit override fun capture(event: VectorAnalyticsEvent) = Unit diff --git a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt index d60cc8f633..081f66d4e6 100644 --- a/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt +++ b/services/analytics/test/src/main/kotlin/io/element/android/services/analytics/test/FakeAnalyticsService.kt @@ -15,6 +15,7 @@ import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.AnalyticsProvider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow class FakeAnalyticsService( isEnabled: Boolean = false, @@ -22,7 +23,7 @@ class FakeAnalyticsService( private val resetLambda: () -> Unit = {}, ) : AnalyticsService { private val isEnabledFlow = MutableStateFlow(isEnabled) - private val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent) + override val didAskUserConsentFlow = MutableStateFlow(didAskUserConsent) val capturedEvents = mutableListOf() val screenEvents = mutableListOf() val trackedErrors = mutableListOf() @@ -30,19 +31,17 @@ class FakeAnalyticsService( override fun getAvailableAnalyticsProviders(): Set = emptySet() - override fun getUserConsent(): Flow = isEnabledFlow + override val userConsentFlow: Flow = isEnabledFlow.asStateFlow() override suspend fun setUserConsent(userConsent: Boolean) { isEnabledFlow.value = userConsent } - override fun didAskUserConsent(): Flow = didAskUserConsentFlow - override suspend fun setDidAskUserConsent() { didAskUserConsentFlow.value = true } - override fun getAnalyticsId(): Flow = MutableStateFlow("") + override val analyticsIdFlow: Flow = MutableStateFlow("") override suspend fun setAnalyticsId(analyticsId: String) { } diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt new file mode 100644 index 0000000000..b9cca40740 --- /dev/null +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistFlowTest.kt @@ -0,0 +1,37 @@ +/* + * 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.tests.konsist + +import androidx.compose.runtime.Composable +import com.lemonappdev.konsist.api.Konsist +import com.lemonappdev.konsist.api.ext.list.withAnnotationOf +import com.lemonappdev.konsist.api.verify.assertFalse +import org.junit.Test + +class KonsistFlowTest { + @Test + fun `flow must be remembered when it is collected as state`() { + // Match + // ```).collectAsState``` + // and + // ```) + // .collectAsState``` + val regex = "(.*)\\)(\n\\s*)*\\.collectAsState".toRegex() + + Konsist + .scopeFromProject() + .functions() + .withAnnotationOf(Composable::class) + .assertFalse( + additionalMessage = "Please check that the flow is remembered when it is collected as state." + + " Only val flows can be not remembered.", + ) { function -> + regex.matches(function.text) + } + } +}