Skip to content

Commit 2963cba

Browse files
committed
Multi accounts - first implementation.
1 parent 812d6db commit 2963cba

File tree

24 files changed

+243
-14
lines changed

24 files changed

+243
-14
lines changed

appnav/src/main/kotlin/io/element/android/appnav/LoggedInAppScopeFlowNode.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class LoggedInAppScopeFlowNode(
5757
), DependencyInjectionGraphOwner {
5858
interface Callback : Plugin {
5959
fun onOpenBugReport()
60+
fun onAddAccount()
6061
}
6162

6263
@Parcelize
@@ -83,6 +84,10 @@ class LoggedInAppScopeFlowNode(
8384
override fun onOpenBugReport() {
8485
plugins<Callback>().forEach { it.onOpenBugReport() }
8586
}
87+
88+
override fun onAddAccount() {
89+
plugins<Callback>().forEach { it.onAddAccount() }
90+
}
8691
}
8792
return createNode<LoggedInFlowNode>(buildContext, listOf(callback))
8893
}

appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ class LoggedInFlowNode(
139139
) {
140140
interface Callback : Plugin {
141141
fun onOpenBugReport()
142+
fun onAddAccount()
142143
}
143144

144145
private val loggedInFlowProcessor = LoggedInEventProcessor(
@@ -395,6 +396,10 @@ class LoggedInFlowNode(
395396
}
396397
is NavTarget.Settings -> {
397398
val callback = object : PreferencesEntryPoint.Callback {
399+
override fun onAddAccount() {
400+
plugins<Callback>().forEach { it.onAddAccount() }
401+
}
402+
398403
override fun onOpenBugReport() {
399404
plugins<Callback>().forEach { it.onOpenBugReport() }
400405
}

appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ class RootFlowNode(
218218
override fun onOpenBugReport() {
219219
backstack.push(NavTarget.BugReport)
220220
}
221+
222+
override fun onAddAccount() {
223+
backstack.push(NavTarget.NotLoggedInFlow(null))
224+
}
221225
}
222226
createNode<LoggedInAppScopeFlowNode>(buildContext, plugins = listOf(inputs, callback))
223227
}

features/home/impl/src/main/kotlin/io/element/android/features/home/impl/HomePresenter.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
2828
import io.element.android.libraries.indicator.api.IndicatorService
2929
import io.element.android.libraries.matrix.api.MatrixClient
3030
import io.element.android.libraries.matrix.api.sync.SyncService
31+
import io.element.android.libraries.sessionstorage.api.SessionStore
3132

3233
@Inject
3334
class HomePresenter(
@@ -39,10 +40,11 @@ class HomePresenter(
3940
private val logoutPresenter: Presenter<DirectLogoutState>,
4041
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
4142
private val featureFlagService: FeatureFlagService,
43+
private val sessionStore: SessionStore,
4244
) : Presenter<HomeState> {
4345
@Composable
4446
override fun present(): HomeState {
45-
val matrixUser = client.userProfile.collectAsState()
47+
val matrixUser by client.userProfile.collectAsState()
4648
val isOnline by syncService.isOnline.collectAsState()
4749
val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false)
4850
val roomListState = roomListPresenter.present()
@@ -59,6 +61,15 @@ class HomePresenter(
5961
// Force a refresh of the profile
6062
client.getUserProfile()
6163
}
64+
LaunchedEffect(matrixUser) {
65+
// Ensure that the profile is always up to date in our
66+
// session storage when it changes
67+
sessionStore.updateUserProfile(
68+
sessionId = matrixUser.userId.value,
69+
displayName = matrixUser.displayName,
70+
avatarUrl = matrixUser.avatarUrl,
71+
)
72+
}
6273
// Avatar indicator
6374
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
6475
val directLogoutState = logoutPresenter.present()
@@ -73,7 +84,7 @@ class HomePresenter(
7384

7485
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
7586
return HomeState(
76-
matrixUser = matrixUser.value,
87+
matrixUser = matrixUser,
7788
showAvatarIndicator = showAvatarIndicator,
7889
hasNetworkConnection = isOnline,
7990
currentHomeNavigationBarItem = currentHomeNavigationBarItem,

features/preferences/api/src/main/kotlin/io/element/android/features/preferences/api/PreferencesEntryPoint.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ interface PreferencesEntryPoint : FeatureEntryPoint {
4141
}
4242

4343
interface Callback : Plugin {
44+
fun onAddAccount()
4445
fun onOpenBugReport()
4546
fun onSecureBackupClick()
4647
fun onOpenRoomNotificationSettings(roomId: RoomId)

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/PreferencesFlowNode.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ class PreferencesFlowNode(
117117
return when (navTarget) {
118118
NavTarget.Root -> {
119119
val callback = object : PreferencesRootNode.Callback {
120+
override fun onAddAccount() {
121+
plugins<PreferencesEntryPoint.Callback>().forEach { it.onAddAccount() }
122+
}
123+
120124
override fun onOpenBugReport() {
121125
plugins<PreferencesEntryPoint.Callback>().forEach { it.onOpenBugReport() }
122126
}

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootEvents.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77

88
package io.element.android.features.preferences.impl.root
99

10+
import io.element.android.libraries.matrix.api.core.SessionId
11+
1012
sealed interface PreferencesRootEvents {
1113
data object OnVersionInfoClick : PreferencesRootEvents
14+
data class SwitchToSession(val sessionId: SessionId) : PreferencesRootEvents
1215
}

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootNode.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class PreferencesRootNode(
3434
private val directLogoutView: DirectLogoutView,
3535
) : Node(buildContext, plugins = plugins) {
3636
interface Callback : Plugin {
37+
fun onAddAccount()
3738
fun onOpenBugReport()
3839
fun onSecureBackupClick()
3940
fun onOpenAnalytics()
@@ -48,6 +49,10 @@ class PreferencesRootNode(
4849
fun onOpenAccountDeactivation()
4950
}
5051

52+
private fun onAddAccount() {
53+
plugins<Callback>().forEach { it.onAddAccount() }
54+
}
55+
5156
private fun onOpenBugReport() {
5257
plugins<Callback>().forEach { it.onOpenBugReport() }
5358
}
@@ -119,6 +124,7 @@ class PreferencesRootNode(
119124
state = state,
120125
modifier = modifier,
121126
onBackClick = this::navigateUp,
127+
onAddAccountClick = this::onAddAccount,
122128
onOpenRageShake = this::onOpenBugReport,
123129
onOpenAnalytics = this::onOpenAnalytics,
124130
onOpenAbout = this::onOpenAbout,

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenter.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,21 @@ import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
2424
import io.element.android.libraries.architecture.Presenter
2525
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
2626
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
27+
import io.element.android.libraries.featureflag.api.FeatureFlagService
28+
import io.element.android.libraries.featureflag.api.FeatureFlags
2729
import io.element.android.libraries.indicator.api.IndicatorService
2830
import io.element.android.libraries.matrix.api.MatrixClient
31+
import io.element.android.libraries.matrix.api.core.UserId
2932
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
33+
import io.element.android.libraries.matrix.api.user.MatrixUser
3034
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
35+
import io.element.android.libraries.sessionstorage.api.SessionStore
3136
import io.element.android.services.analytics.api.AnalyticsService
37+
import kotlinx.collections.immutable.persistentListOf
38+
import kotlinx.collections.immutable.toPersistentList
3239
import kotlinx.coroutines.CoroutineScope
3340
import kotlinx.coroutines.flow.launchIn
41+
import kotlinx.coroutines.flow.map
3442
import kotlinx.coroutines.flow.onEach
3543
import kotlinx.coroutines.launch
3644

@@ -45,6 +53,8 @@ class PreferencesRootPresenter(
4553
private val directLogoutPresenter: Presenter<DirectLogoutState>,
4654
private val showDeveloperSettingsProvider: ShowDeveloperSettingsProvider,
4755
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
56+
private val featureFlagService: FeatureFlagService,
57+
private val sessionStore: SessionStore,
4858
) : Presenter<PreferencesRootState> {
4959
@Composable
5060
override fun present(): PreferencesRootState {
@@ -55,6 +65,25 @@ class PreferencesRootPresenter(
5565
matrixClient.getUserProfile()
5666
}
5767

68+
val isMultiAccountEnabled by remember {
69+
featureFlagService.isFeatureEnabledFlow(FeatureFlags.MultiAccount)
70+
}.collectAsState(initial = false)
71+
72+
val otherSessions by remember {
73+
sessionStore.sessionsFlow().map { list ->
74+
list
75+
.filter { it.userId != matrixClient.sessionId.value }
76+
.map {
77+
MatrixUser(
78+
userId = UserId(it.userId),
79+
displayName = it.userDisplayName,
80+
avatarUrl = it.userAvatarUrl,
81+
)
82+
}
83+
.toPersistentList()
84+
}
85+
}.collectAsState(initial = persistentListOf())
86+
5887
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
5988
val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() }
6089

@@ -96,13 +125,18 @@ class PreferencesRootPresenter(
96125
is PreferencesRootEvents.OnVersionInfoClick -> {
97126
showDeveloperSettingsProvider.unlockDeveloperSettings(coroutineScope)
98127
}
128+
is PreferencesRootEvents.SwitchToSession -> coroutineScope.launch {
129+
sessionStore.setLatestSession(event.sessionId.value)
130+
}
99131
}
100132
}
101133

102134
return PreferencesRootState(
103135
myUser = matrixUser.value,
104136
version = versionFormatter.get(),
105137
deviceId = matrixClient.deviceId,
138+
isMultiAccountEnabled = isMultiAccountEnabled,
139+
otherSessions = otherSessions,
106140
showSecureBackup = !canVerifyUserSession,
107141
showSecureBackupBadge = showSecureBackupIndicator,
108142
accountManagementUrl = accountManagementUrl.value,

features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootState.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,14 @@ import io.element.android.features.logout.api.direct.DirectLogoutState
1111
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
1212
import io.element.android.libraries.matrix.api.core.DeviceId
1313
import io.element.android.libraries.matrix.api.user.MatrixUser
14+
import kotlinx.collections.immutable.ImmutableList
1415

1516
data class PreferencesRootState(
1617
val myUser: MatrixUser,
1718
val version: String,
1819
val deviceId: DeviceId?,
20+
val isMultiAccountEnabled: Boolean,
21+
val otherSessions: ImmutableList<MatrixUser>,
1922
val showSecureBackup: Boolean,
2023
val showSecureBackupBadge: Boolean,
2124
val accountManagementUrl: String?,

0 commit comments

Comments
 (0)