mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 16:22:49 +00:00
Move user status next to the current account header.
Also make the other account section collapsable
This commit is contained in:
+1
@@ -13,4 +13,5 @@ import io.element.android.libraries.matrix.api.core.SessionId
|
||||
sealed interface PreferencesRootEvent {
|
||||
data object OnVersionInfoClick : PreferencesRootEvent
|
||||
data class SwitchToSession(val sessionId: SessionId) : PreferencesRootEvent
|
||||
data object ToggleOtherAccountsExpanded : PreferencesRootEvent
|
||||
}
|
||||
|
||||
+9
@@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -57,6 +58,7 @@ class PreferencesRootPresenter(
|
||||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val sessionStore: SessionStore,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val sessionEnterpriseService: SessionEnterpriseService,
|
||||
private val userStatusPresenter: Presenter<UserStatusState>,
|
||||
) : Presenter<PreferencesRootState> {
|
||||
@@ -71,6 +73,9 @@ class PreferencesRootPresenter(
|
||||
val isMultiAccountEnabled by remember {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.MultiAccount)
|
||||
}.collectAsState(initial = false)
|
||||
val isOtherAccountsSectionExpanded by remember {
|
||||
appPreferencesStore.isOtherAccountsExpandedFlow()
|
||||
}.collectAsState(initial = true)
|
||||
val showLinkNewDevice by remember {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.QrCodeLogin)
|
||||
}.collectAsState(initial = false)
|
||||
@@ -138,6 +143,9 @@ class PreferencesRootPresenter(
|
||||
is PreferencesRootEvent.SwitchToSession -> coroutineScope.launch {
|
||||
sessionStore.setLatestSession(event.sessionId.value)
|
||||
}
|
||||
PreferencesRootEvent.ToggleOtherAccountsExpanded -> coroutineScope.launch {
|
||||
appPreferencesStore.setOtherAccountsExpanded(!isOtherAccountsSectionExpanded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +154,7 @@ class PreferencesRootPresenter(
|
||||
userStatusState = userStatusState,
|
||||
version = remember { versionFormatter.get() },
|
||||
isMultiAccountEnabled = isMultiAccountEnabled,
|
||||
isOtherAccountsSectionExpanded = isOtherAccountsSectionExpanded,
|
||||
otherSessions = otherSessions,
|
||||
showSecureBackup = !canVerifyUserSession,
|
||||
showSecureBackupBadge = showSecureBackupIndicator,
|
||||
|
||||
+1
@@ -19,6 +19,7 @@ data class PreferencesRootState(
|
||||
val userStatusState: UserStatusState?,
|
||||
val version: String,
|
||||
val isMultiAccountEnabled: Boolean,
|
||||
val isOtherAccountsSectionExpanded: Boolean,
|
||||
val otherSessions: ImmutableList<MatrixUser>,
|
||||
val showSecureBackup: Boolean,
|
||||
val showSecureBackupBadge: Boolean,
|
||||
|
||||
+3
@@ -47,6 +47,7 @@ open class PreferencesRootStatePreviewParam : PreviewParameterProvider<Preferenc
|
||||
),
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = aMatrixUserList().drop(1).take(3),
|
||||
accountManagementUrl = "aUrl",
|
||||
showSecureBackup = true,
|
||||
@@ -74,6 +75,7 @@ fun aPreferencesRootState(
|
||||
myUser: MatrixUser = aMatrixUser(),
|
||||
version: String = "Version 1.1 (1)",
|
||||
isMultiAccountEnabled: Boolean = false,
|
||||
isOtherAccountsSectionExpanded: Boolean = false,
|
||||
otherSessions: List<MatrixUser> = emptyList(),
|
||||
showSecureBackup: Boolean = false,
|
||||
showSecureBackupBadge: Boolean = false,
|
||||
@@ -93,6 +95,7 @@ fun aPreferencesRootState(
|
||||
myUser = myUser,
|
||||
version = version,
|
||||
isMultiAccountEnabled = isMultiAccountEnabled,
|
||||
isOtherAccountsSectionExpanded = isOtherAccountsSectionExpanded,
|
||||
otherSessions = otherSessions.toImmutableList(),
|
||||
showSecureBackup = showSecureBackup,
|
||||
showSecureBackupBadge = showSecureBackupBadge,
|
||||
|
||||
+112
-31
@@ -9,17 +9,28 @@
|
||||
package io.element.android.features.preferences.impl.root
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.semantics.stateDescription
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -33,13 +44,16 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionIndicator
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarRow
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.components.preferences.PreferencePage
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItemStyle
|
||||
@@ -51,7 +65,9 @@ import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
fun PreferencesRootView(
|
||||
@@ -91,19 +107,22 @@ fun PreferencesRootView(
|
||||
},
|
||||
matrixUser = state.myUser,
|
||||
)
|
||||
if (state.userStatusState != null) {
|
||||
UserStatusSection(
|
||||
userStatusState = state.userStatusState,
|
||||
emojiPickerRenderer = emojiPickerRenderer,
|
||||
)
|
||||
}
|
||||
if (state.isMultiAccountEnabled) {
|
||||
MultiAccountSection(
|
||||
state = state,
|
||||
onAddAccountClick = onAddAccountClick,
|
||||
)
|
||||
}
|
||||
if (state.userStatusState != null) {
|
||||
UserStatusSection(
|
||||
userStatusState = state.userStatusState,
|
||||
emojiPickerRenderer = emojiPickerRenderer,
|
||||
showTopDivider = !state.isMultiAccountEnabled,
|
||||
)
|
||||
}
|
||||
HorizontalDivider(
|
||||
thickness = 8.dp,
|
||||
color = ElementTheme.colors.bgSubtleSecondary,
|
||||
)
|
||||
// 'Account' section
|
||||
ManageAccountSection(
|
||||
state = state,
|
||||
@@ -163,25 +182,21 @@ private fun BoxScope.UserStatusUpdateIndicator(updateStatusAction: AsyncAction<U
|
||||
private fun ColumnScope.UserStatusSection(
|
||||
userStatusState: UserStatusState,
|
||||
emojiPickerRenderer: EmojiPickerRenderer,
|
||||
showTopDivider: Boolean,
|
||||
) {
|
||||
if (showTopDivider) {
|
||||
HorizontalDivider(
|
||||
thickness = 8.dp,
|
||||
color = ElementTheme.colors.bgSubtleSecondary,
|
||||
)
|
||||
}
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
color = ElementTheme.colors.bgSubtleSecondary,
|
||||
)
|
||||
UserStatusView(
|
||||
state = userStatusState,
|
||||
emojiPickerRenderer = emojiPickerRenderer,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
color = ElementTheme.colors.bgSubtleSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/G1xy0HDZKJf5TCRFmKb5d5/Compound-Android-Components?node-id=5414-4759
|
||||
*/
|
||||
@Composable
|
||||
private fun ColumnScope.MultiAccountSection(
|
||||
state: PreferencesRootState,
|
||||
@@ -191,18 +206,88 @@ private fun ColumnScope.MultiAccountSection(
|
||||
thickness = 8.dp,
|
||||
color = ElementTheme.colors.bgSubtleSecondary,
|
||||
)
|
||||
state.otherSessions.forEach { matrixUser ->
|
||||
MatrixUserRow(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
state.eventSink(PreferencesRootEvent.SwitchToSession(matrixUser.userId))
|
||||
if (state.otherSessions.isEmpty()) {
|
||||
AddAccountItem(onAddAccountClick)
|
||||
} else {
|
||||
val expandedStateDescription = if (state.isOtherAccountsSectionExpanded) {
|
||||
stringResource(CommonStrings.a11y_state_expanded)
|
||||
} else {
|
||||
stringResource(CommonStrings.a11y_state_collapsed)
|
||||
}
|
||||
ListItem(
|
||||
modifier = Modifier.semantics {
|
||||
stateDescription = expandedStateDescription
|
||||
},
|
||||
content = { Text(stringResource(CommonStrings.common_switch_account)) },
|
||||
onClick = { state.eventSink(PreferencesRootEvent.ToggleOtherAccountsExpanded) },
|
||||
trailingContent = ListItemContent.Custom { _ ->
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = !state.isOtherAccountsSectionExpanded,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
AvatarRow(
|
||||
avatarDataList = state.otherSessions
|
||||
.take(3)
|
||||
.map { it.getAvatarData(AvatarSize.OtherAccountItem) }
|
||||
.toImmutableList(),
|
||||
avatarType = AvatarType.User,
|
||||
lastOnTop = true,
|
||||
)
|
||||
}
|
||||
// Animate the chevron icon to rotate when the section is expanded/collapsed
|
||||
val rotation: Float by animateFloatAsState(
|
||||
targetValue = if (state.isOtherAccountsSectionExpanded) 180f else 0f,
|
||||
animationSpec = tween(
|
||||
delayMillis = 0,
|
||||
durationMillis = 300,
|
||||
),
|
||||
label = "chevron"
|
||||
)
|
||||
Icon(
|
||||
modifier = Modifier.rotate(rotation),
|
||||
imageVector = if (state.isOtherAccountsSectionExpanded) {
|
||||
CompoundIcons.ChevronUp()
|
||||
} else {
|
||||
CompoundIcons.ChevronDown()
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
.padding(top = 2.dp, bottom = 2.dp, end = 8.dp),
|
||||
matrixUser = matrixUser,
|
||||
avatarSize = AvatarSize.AccountItem,
|
||||
verticalSpaceWidth = 16.dp,
|
||||
},
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = state.isOtherAccountsSectionExpanded,
|
||||
) {
|
||||
Column {
|
||||
HorizontalDivider(
|
||||
thickness = 1.dp,
|
||||
color = ElementTheme.colors.bgSubtleSecondary,
|
||||
)
|
||||
state.otherSessions.forEach { matrixUser ->
|
||||
MatrixUserRow(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
state.eventSink(PreferencesRootEvent.SwitchToSession(matrixUser.userId))
|
||||
}
|
||||
.padding(top = 2.dp, bottom = 2.dp, end = 8.dp),
|
||||
matrixUser = matrixUser,
|
||||
avatarSize = AvatarSize.AccountItem,
|
||||
verticalSpaceWidth = 16.dp,
|
||||
)
|
||||
}
|
||||
AddAccountItem(onAddAccountClick)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddAccountItem(onAddAccountClick: () -> Unit) {
|
||||
ListItem(
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Plus())),
|
||||
content = {
|
||||
@@ -210,10 +295,6 @@ private fun ColumnScope.MultiAccountSection(
|
||||
},
|
||||
onClick = onAddAccountClick,
|
||||
)
|
||||
HorizontalDivider(
|
||||
thickness = 8.dp,
|
||||
color = ElementTheme.colors.bgSubtleSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
+44
@@ -37,17 +37,21 @@ import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
@@ -82,6 +86,7 @@ class PreferencesRootPresenterTest {
|
||||
)
|
||||
assertThat(initialState.version).isEqualTo("A Version")
|
||||
assertThat(initialState.isMultiAccountEnabled).isFalse()
|
||||
assertThat(initialState.isOtherAccountsSectionExpanded).isTrue()
|
||||
assertThat(initialState.otherSessions).isEmpty()
|
||||
assertThat(initialState.version).isEqualTo("A Version")
|
||||
val loadedState = awaitItem()
|
||||
@@ -304,6 +309,43 @@ class PreferencesRootPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - other accounts section is collapsed if the preference says so`() = runTest {
|
||||
createPresenter(
|
||||
matrixClient = FakeMatrixClient(
|
||||
canDeactivateAccountResult = { true },
|
||||
accountManagementUrlResult = { Result.success(null) },
|
||||
),
|
||||
appPreferencesStore = InMemoryAppPreferencesStore(isOtherAccountsExpanded = false),
|
||||
).test {
|
||||
consumeItemsUntilPredicate { !it.isOtherAccountsSectionExpanded }
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - toggle other accounts expanded stores the new value`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore()
|
||||
createPresenter(
|
||||
matrixClient = FakeMatrixClient(
|
||||
canDeactivateAccountResult = { true },
|
||||
accountManagementUrlResult = { Result.success(null) },
|
||||
),
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
).test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.isOtherAccountsSectionExpanded).isTrue()
|
||||
initialState.eventSink(PreferencesRootEvent.ToggleOtherAccountsExpanded)
|
||||
val collapsedState = consumeItemsUntilPredicate { !it.isOtherAccountsSectionExpanded }.last()
|
||||
assertThat(appPreferencesStore.isOtherAccountsExpandedFlow().first()).isFalse()
|
||||
// Toggling again must expand the section back
|
||||
collapsedState.eventSink(PreferencesRootEvent.ToggleOtherAccountsExpanded)
|
||||
consumeItemsUntilPredicate { it.isOtherAccountsSectionExpanded }
|
||||
assertThat(appPreferencesStore.isOtherAccountsExpandedFlow().first()).isTrue()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - link new device`() = runTest {
|
||||
createPresenter(
|
||||
@@ -333,6 +375,7 @@ class PreferencesRootPresenterTest {
|
||||
indicatorService: IndicatorService = FakeIndicatorService(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
sessionEnterpriseService: SessionEnterpriseService = FakeSessionEnterpriseService(),
|
||||
) = PreferencesRootPresenter(
|
||||
matrixClient = matrixClient,
|
||||
@@ -346,6 +389,7 @@ class PreferencesRootPresenterTest {
|
||||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||
featureFlagService = featureFlagService,
|
||||
sessionStore = sessionStore,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
sessionEnterpriseService = sessionEnterpriseService,
|
||||
userStatusPresenter = { aUserStatusState() },
|
||||
)
|
||||
|
||||
+66
@@ -27,6 +27,8 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.assertNoNodeWithText
|
||||
import io.element.android.tests.testutils.assertNodeWithTextIsDisplayed
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
@@ -71,6 +73,7 @@ class PreferencesRootViewTest : RobolectricTest() {
|
||||
setView(
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = listOf(
|
||||
aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
@@ -84,6 +87,69 @@ class PreferencesRootViewTest : RobolectricTest() {
|
||||
eventsRecorder.assertSingle(PreferencesRootEvent.SwitchToSession(A_USER_ID_2))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Switch accounts sends a ToggleOtherAccountsExpanded`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>()
|
||||
setView(
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = false,
|
||||
otherSessions = listOf(
|
||||
aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
clickOn(CommonStrings.common_switch_account)
|
||||
eventsRecorder.assertSingle(PreferencesRootEvent.ToggleOtherAccountsExpanded)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when the other accounts section is collapsed, other sessions and add account are not shown`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||
setView(
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = false,
|
||||
otherSessions = listOf(
|
||||
aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
assertNodeWithTextIsDisplayed(CommonStrings.common_switch_account)
|
||||
onNodeWithText("Bob").assertDoesNotExist()
|
||||
assertNoNodeWithText(CommonStrings.common_add_another_account)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `click on Add account of the expanded section invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||
ensureCalledOnce { callback ->
|
||||
setView(
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = listOf(
|
||||
aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onAddAccountClick = callback,
|
||||
)
|
||||
clickOn(CommonStrings.common_add_another_account)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `click on Add account invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||
|
||||
+1
@@ -82,6 +82,7 @@ enum class AvatarSize(val dp: Dp) {
|
||||
LeaveSpaceRoom(32.dp),
|
||||
SelectParentSpace(32.dp),
|
||||
AccountItem(32.dp),
|
||||
OtherAccountItem(20.dp),
|
||||
LocationPin(32.dp),
|
||||
ActiveCallItem(20.dp)
|
||||
}
|
||||
|
||||
+8
@@ -44,6 +44,14 @@ interface AppPreferencesStore {
|
||||
/** The name of the chosen theme, or `null` when the user has not picked one and the system setting should be followed. */
|
||||
fun getThemeFlow(): Flow<String?>
|
||||
|
||||
/**
|
||||
* @param expanded true to expand the other accounts section.
|
||||
*/
|
||||
suspend fun setOtherAccountsExpanded(expanded: Boolean)
|
||||
|
||||
/** Whether the other accounts section is expanded; defaults to `true`. */
|
||||
fun isOtherAccountsExpandedFlow(): Flow<Boolean>
|
||||
|
||||
/**
|
||||
* @param value the distance in metres the user must move before a new live location is published.
|
||||
*/
|
||||
|
||||
+13
@@ -33,6 +33,7 @@ private val developerModeKey = booleanPreferencesKey("developerMode")
|
||||
private val customElementCallBaseUrlKey = stringPreferencesKey("elementCallBaseUrl")
|
||||
private val themeKey = stringPreferencesKey("theme")
|
||||
private val hideInviteAvatarsKey = booleanPreferencesKey("hideInviteAvatars")
|
||||
private val otherAccountsExpandedKey = booleanPreferencesKey("otherAccountsExpanded")
|
||||
private val timelineMediaPreviewValueKey = stringPreferencesKey("timelineMediaPreviewValue")
|
||||
private val liveLocationMinimumDistanceUpdateKey = intPreferencesKey("liveLocationMinimumDistanceUpdate")
|
||||
private val logLevelKey = stringPreferencesKey("logLevel")
|
||||
@@ -97,6 +98,18 @@ class DefaultAppPreferencesStore(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setOtherAccountsExpanded(expanded: Boolean) {
|
||||
store.edit { prefs ->
|
||||
prefs[otherAccountsExpandedKey] = expanded
|
||||
}
|
||||
}
|
||||
|
||||
override fun isOtherAccountsExpandedFlow(): Flow<Boolean> {
|
||||
return store.data.map { prefs ->
|
||||
prefs[otherAccountsExpandedKey] ?: true
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setLiveLocationMinimumDistanceInMetersUpdate(value: Int) {
|
||||
store.edit { prefs ->
|
||||
prefs[liveLocationMinimumDistanceUpdateKey] = value
|
||||
|
||||
+18
@@ -120,6 +120,24 @@ class DefaultAppPreferencesStoreTest {
|
||||
assertThat(history.last()).isEqualTo("https://server5.org")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `other accounts section is expanded by default`() = runTest {
|
||||
val store = createStore()
|
||||
|
||||
assertThat(store.isOtherAccountsExpandedFlow().first()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `other accounts expanded state persists updates`() = runTest {
|
||||
val store = createStore()
|
||||
|
||||
store.setOtherAccountsExpanded(false)
|
||||
assertThat(store.isOtherAccountsExpandedFlow().first()).isFalse()
|
||||
|
||||
store.setOtherAccountsExpanded(true)
|
||||
assertThat(store.isOtherAccountsExpandedFlow().first()).isTrue()
|
||||
}
|
||||
|
||||
private fun createStore() = DefaultAppPreferencesStore(
|
||||
buildMeta = buildMeta,
|
||||
preferenceDataStoreFactory = FakePreferenceDataStoreFactory(),
|
||||
|
||||
+10
@@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.updateAndGet
|
||||
|
||||
class InMemoryAppPreferencesStore(
|
||||
isDeveloperModeEnabled: Boolean = false,
|
||||
isOtherAccountsExpanded: Boolean = true,
|
||||
customElementCallBaseUrl: String? = null,
|
||||
hideInviteAvatars: Boolean? = null,
|
||||
timelineMediaPreviewValue: MediaPreviewValue? = null,
|
||||
@@ -37,6 +38,7 @@ class InMemoryAppPreferencesStore(
|
||||
callRingtoneDisplayName: String? = null,
|
||||
) : AppPreferencesStore {
|
||||
private val isDeveloperModeEnabled = MutableStateFlow(isDeveloperModeEnabled)
|
||||
private val isOtherAccountsExpanded = MutableStateFlow(isOtherAccountsExpanded)
|
||||
private val customElementCallBaseUrl = MutableStateFlow(customElementCallBaseUrl)
|
||||
private val theme = MutableStateFlow(theme)
|
||||
private val liveLocationMinimumDistanceUpdate = MutableStateFlow(liveLocationMinimumDistanceUpdate)
|
||||
@@ -60,6 +62,14 @@ class InMemoryAppPreferencesStore(
|
||||
return isDeveloperModeEnabled
|
||||
}
|
||||
|
||||
override suspend fun setOtherAccountsExpanded(expanded: Boolean) {
|
||||
isOtherAccountsExpanded.value = expanded
|
||||
}
|
||||
|
||||
override fun isOtherAccountsExpandedFlow(): Flow<Boolean> {
|
||||
return isOtherAccountsExpanded
|
||||
}
|
||||
|
||||
override suspend fun setCustomElementCallBaseUrl(string: String?) {
|
||||
customElementCallBaseUrl.tryEmit(string)
|
||||
}
|
||||
|
||||
@@ -10,4 +10,5 @@
|
||||
<string name="a11y_state_collapsed">"Collapsed"</string>
|
||||
<string name="a11y_active">Active</string>
|
||||
<string name="a11y_inactive">Inactive</string>
|
||||
<string name="common_switch_account">Switch accounts</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user