Wire UserStatusPresenter and UserStatusRow into PreferencesRoot

This commit is contained in:
ganfra
2026-06-12 15:48:54 +02:00
parent af5a237b4a
commit 6c34d647ca
7 changed files with 48 additions and 6 deletions
@@ -21,6 +21,7 @@ import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.enterprise.api.SessionEnterpriseService
import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.preferences.impl.userstatus.UserStatusState
import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@@ -57,9 +58,11 @@ class PreferencesRootPresenter(
private val featureFlagService: FeatureFlagService, private val featureFlagService: FeatureFlagService,
private val sessionStore: SessionStore, private val sessionStore: SessionStore,
private val sessionEnterpriseService: SessionEnterpriseService, private val sessionEnterpriseService: SessionEnterpriseService,
private val userStatusPresenter: Presenter<UserStatusState>,
) : Presenter<PreferencesRootState> { ) : Presenter<PreferencesRootState> {
@Composable @Composable
override fun present(): PreferencesRootState { override fun present(): PreferencesRootState {
val userStatusState = userStatusPresenter.present()
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val matrixUser = matrixClient.userProfile.collectAsState() val matrixUser = matrixClient.userProfile.collectAsState()
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -137,6 +140,7 @@ class PreferencesRootPresenter(
return PreferencesRootState( return PreferencesRootState(
myUser = matrixUser.value, myUser = matrixUser.value,
userStatusState = userStatusState,
version = remember { versionFormatter.get() }, version = remember { versionFormatter.get() },
deviceId = matrixClient.deviceId, deviceId = matrixClient.deviceId,
isMultiAccountEnabled = isMultiAccountEnabled, isMultiAccountEnabled = isMultiAccountEnabled,
@@ -9,6 +9,7 @@
package io.element.android.features.preferences.impl.root package io.element.android.features.preferences.impl.root
import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.preferences.impl.userstatus.UserStatusState
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -16,6 +17,7 @@ import kotlinx.collections.immutable.ImmutableList
data class PreferencesRootState( data class PreferencesRootState(
val myUser: MatrixUser, val myUser: MatrixUser,
val userStatusState: UserStatusState,
val version: String, val version: String,
val deviceId: DeviceId?, val deviceId: DeviceId?,
val isMultiAccountEnabled: Boolean, val isMultiAccountEnabled: Boolean,
@@ -11,6 +11,8 @@ package io.element.android.features.preferences.impl.root
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.logout.api.direct.DirectLogoutState import io.element.android.features.logout.api.direct.DirectLogoutState
import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState
import io.element.android.features.preferences.impl.userstatus.UserStatusState
import io.element.android.features.preferences.impl.userstatus.aUserStatusState
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -86,6 +88,7 @@ fun aPreferencesRootState(
nbOfBlockedUsers: Int = 0, nbOfBlockedUsers: Int = 0,
showLabsItem: Boolean = false, showLabsItem: Boolean = false,
directLogoutState: DirectLogoutState = aDirectLogoutState(), directLogoutState: DirectLogoutState = aDirectLogoutState(),
userStatusState: UserStatusState = aUserStatusState(),
snackbarMessage: SnackbarMessage? = null, snackbarMessage: SnackbarMessage? = null,
eventSink: (PreferencesRootEvent) -> Unit = {}, eventSink: (PreferencesRootEvent) -> Unit = {},
) = PreferencesRootState( ) = PreferencesRootState(
@@ -105,6 +108,7 @@ fun aPreferencesRootState(
nbOfBlockedUsers = nbOfBlockedUsers, nbOfBlockedUsers = nbOfBlockedUsers,
showLabsItem = showLabsItem, showLabsItem = showLabsItem,
directLogoutState = directLogoutState, directLogoutState = directLogoutState,
userStatusState = userStatusState,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
eventSink = eventSink, eventSink = eventSink,
) )
@@ -10,6 +10,7 @@ package io.element.android.features.preferences.impl.root
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -23,6 +24,7 @@ import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.features.preferences.impl.user.UserPreferences import io.element.android.features.preferences.impl.user.UserPreferences
import io.element.android.features.preferences.impl.userstatus.UserStatusRow
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.list.ListItemContent import io.element.android.libraries.designsystem.components.list.ListItemContent
@@ -80,15 +82,18 @@ fun PreferencesRootView(
}, },
matrixUser = state.myUser, matrixUser = state.myUser,
) )
HorizontalDivider()
UserStatusRow(
state = state.userStatusState,
modifier = Modifier.fillMaxWidth(),
)
HorizontalDivider()
if (state.isMultiAccountEnabled) { if (state.isMultiAccountEnabled) {
MultiAccountSection( MultiAccountSection(
state = state, state = state,
onAddAccountClick = onAddAccountClick, onAddAccountClick = onAddAccountClick,
) )
} else {
HorizontalDivider()
} }
// User status will be added here
// 'Account' section // 'Account' section
ManageAccountSection( ManageAccountSection(
state = state, state = state,
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2026 Element Creations 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.features.preferences.impl.userstatus
import io.element.android.libraries.matrix.api.user.DisplayedStatus
fun aUserStatusState(
displayedStatus: DisplayedStatus? = null,
pickerState: UserStatusPickerState = UserStatusPickerState.Hidden,
eventSink: (UserStatusEvent) -> Unit = {},
) = UserStatusState(
displayedStatus = displayedStatus,
pickerState = pickerState,
eventSink = eventSink,
)
@@ -15,6 +15,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.enterprise.api.SessionEnterpriseService import io.element.android.features.enterprise.api.SessionEnterpriseService
import io.element.android.features.enterprise.test.FakeSessionEnterpriseService import io.element.android.features.enterprise.test.FakeSessionEnterpriseService
import io.element.android.features.logout.api.direct.aDirectLogoutState import io.element.android.features.logout.api.direct.aDirectLogoutState
import io.element.android.features.preferences.impl.userstatus.aUserStatusState
import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.core.meta.BuildType
@@ -346,5 +347,6 @@ class PreferencesRootPresenterTest {
featureFlagService = featureFlagService, featureFlagService = featureFlagService,
sessionStore = sessionStore, sessionStore = sessionStore,
sessionEnterpriseService = sessionEnterpriseService, sessionEnterpriseService = sessionEnterpriseService,
userStatusPresenter = { aUserStatusState() },
) )
} }
@@ -12,8 +12,11 @@ package io.element.android.features.preferences.impl.root
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.v2.runAndroidComposeUiTest import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import io.element.android.features.preferences.impl.R import io.element.android.features.preferences.impl.R
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -281,7 +284,8 @@ class PreferencesRootViewTest : RobolectricTest() {
), ),
onOpenDeveloperSettings = callback, onOpenDeveloperSettings = callback,
) )
clickOn(CommonStrings.common_developer_options) val text = activity!!.getString(CommonStrings.common_developer_options)
onNode(hasText(text) and hasClickAction()).performScrollTo().performClick()
} }
} }
@@ -404,7 +408,8 @@ class PreferencesRootViewTest : RobolectricTest() {
), ),
onDeactivateClick = callback, onDeactivateClick = callback,
) )
clickOn(CommonStrings.action_delete_account) val text = activity!!.getString(CommonStrings.action_delete_account)
onNode(hasText(text) and hasClickAction()).performScrollTo().performClick()
} }
} }
@@ -430,7 +435,7 @@ class PreferencesRootViewTest : RobolectricTest() {
eventSink = eventsRecorder, eventSink = eventsRecorder,
), ),
) )
onNodeWithText(version).performClick() onNodeWithText(version).performScrollTo().performClick()
eventsRecorder.assertSingle(PreferencesRootEvent.OnVersionInfoClick) eventsRecorder.assertSingle(PreferencesRootEvent.OnVersionInfoClick)
} }
} }