Skip to content

Commit 30f6db8

Browse files
committed
Add accountselect modules
1 parent 408ca21 commit 30f6db8

File tree

12 files changed

+399
-0
lines changed

12 files changed

+399
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
plugins {
8+
id("io.element.android-library")
9+
}
10+
11+
android {
12+
namespace = "io.element.android.libraries.accountselect.api"
13+
}
14+
15+
dependencies {
16+
implementation(projects.libraries.architecture)
17+
implementation(projects.libraries.matrix.api)
18+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.accountselect.api
9+
10+
import com.bumble.appyx.core.modality.BuildContext
11+
import com.bumble.appyx.core.node.Node
12+
import com.bumble.appyx.core.plugin.Plugin
13+
import io.element.android.libraries.architecture.FeatureEntryPoint
14+
import io.element.android.libraries.matrix.api.core.SessionId
15+
16+
interface AccountSelectEntryPoint : FeatureEntryPoint {
17+
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
18+
19+
interface NodeBuilder {
20+
fun callback(callback: Callback): NodeBuilder
21+
fun build(): Node
22+
}
23+
24+
interface Callback : Plugin {
25+
fun onAccountSelected(sessionId: SessionId)
26+
fun onCancel()
27+
}
28+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import extension.setupAnvil
2+
3+
/*
4+
* Copyright 2025 New Vector Ltd.
5+
*
6+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
7+
* Please see LICENSE files in the repository root for full details.
8+
*/
9+
10+
plugins {
11+
id("io.element.android-compose-library")
12+
}
13+
14+
android {
15+
namespace = "io.element.android.libraries.accountselect.impl"
16+
}
17+
18+
setupAnvil()
19+
20+
dependencies {
21+
implementation(projects.libraries.core)
22+
implementation(projects.libraries.androidutils)
23+
implementation(projects.libraries.architecture)
24+
implementation(projects.libraries.matrix.api)
25+
implementation(projects.libraries.matrixui)
26+
implementation(projects.libraries.sessionStorage.api)
27+
implementation(projects.libraries.designsystem)
28+
implementation(projects.libraries.uiStrings)
29+
api(projects.libraries.accountselect.api)
30+
31+
testImplementation(libs.test.junit)
32+
testImplementation(libs.coroutines.test)
33+
testImplementation(libs.molecule.runtime)
34+
testImplementation(libs.test.truth)
35+
testImplementation(libs.test.turbine)
36+
testImplementation(projects.libraries.matrix.test)
37+
testImplementation(projects.libraries.sessionStorage.implMemory)
38+
testImplementation(projects.tests.testutils)
39+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.accountselect.impl
9+
10+
sealed interface AccountSelectEvents
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.accountselect.impl
9+
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Modifier
12+
import com.bumble.appyx.core.modality.BuildContext
13+
import com.bumble.appyx.core.node.Node
14+
import com.bumble.appyx.core.plugin.Plugin
15+
import dagger.assisted.Assisted
16+
import dagger.assisted.AssistedInject
17+
import io.element.android.anvilannotations.ContributesNode
18+
import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint
19+
import io.element.android.libraries.di.AppScope
20+
import io.element.android.libraries.matrix.api.core.SessionId
21+
22+
@ContributesNode(AppScope::class)
23+
class AccountSelectNode @AssistedInject constructor(
24+
@Assisted buildContext: BuildContext,
25+
@Assisted plugins: List<Plugin>,
26+
private val presenter: AccountSelectPresenter,
27+
) : Node(buildContext, plugins = plugins) {
28+
private val callbacks = plugins.filterIsInstance<AccountSelectEntryPoint.Callback>()
29+
30+
private fun onDismiss() {
31+
callbacks.forEach { it.onCancel() }
32+
}
33+
34+
private fun onAccountSelected(sessionId: SessionId) {
35+
callbacks.forEach { it.onAccountSelected(sessionId) }
36+
}
37+
38+
@Composable
39+
override fun View(modifier: Modifier) {
40+
val state = presenter.present()
41+
AccountSelectView(
42+
state = state,
43+
onDismiss = ::onDismiss,
44+
onAccountSelected = ::onAccountSelected,
45+
modifier = modifier,
46+
)
47+
}
48+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.accountselect.impl
9+
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.getValue
12+
import androidx.compose.runtime.produceState
13+
import io.element.android.libraries.architecture.Presenter
14+
import io.element.android.libraries.matrix.api.core.UserId
15+
import io.element.android.libraries.matrix.api.user.MatrixUser
16+
import io.element.android.libraries.sessionstorage.api.SessionStore
17+
import kotlinx.collections.immutable.persistentListOf
18+
import kotlinx.collections.immutable.toPersistentList
19+
import javax.inject.Inject
20+
21+
class AccountSelectPresenter @Inject constructor(
22+
private val sessionStore: SessionStore,
23+
) : Presenter<AccountSelectState> {
24+
@Composable
25+
override fun present(): AccountSelectState {
26+
val accounts by produceState(persistentListOf()) {
27+
// Do not use sessionStore.sessionsFlow() to not make it change when an account is selected.
28+
value = sessionStore.getAllSessions()
29+
.map {
30+
MatrixUser(
31+
userId = UserId(it.userId),
32+
displayName = it.userDisplayName,
33+
avatarUrl = it.userAvatarUrl,
34+
)
35+
}
36+
.toPersistentList()
37+
}
38+
39+
fun handleEvents(event: AccountSelectEvents) {}
40+
41+
return AccountSelectState(
42+
accounts = accounts,
43+
eventSink = ::handleEvents
44+
)
45+
}
46+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.accountselect.impl
9+
10+
import io.element.android.libraries.matrix.api.user.MatrixUser
11+
import kotlinx.collections.immutable.ImmutableList
12+
13+
data class AccountSelectState(
14+
val accounts: ImmutableList<MatrixUser>,
15+
val eventSink: (AccountSelectEvents) -> Unit
16+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.accountselect.impl
9+
10+
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
11+
import io.element.android.libraries.matrix.api.user.MatrixUser
12+
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
13+
import kotlinx.collections.immutable.toPersistentList
14+
15+
open class AccountSelectStateProvider : PreviewParameterProvider<AccountSelectState> {
16+
override val values: Sequence<AccountSelectState>
17+
get() = sequenceOf(
18+
anAccountSelectState(),
19+
anAccountSelectState(accounts = aMatrixUserList()),
20+
)
21+
}
22+
23+
private fun anAccountSelectState(
24+
accounts: List<MatrixUser> = listOf(),
25+
eventSink: (AccountSelectEvents) -> Unit = {},
26+
) = AccountSelectState(
27+
accounts = accounts.toPersistentList(),
28+
eventSink = eventSink,
29+
)
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.accountselect.impl
9+
10+
import androidx.activity.compose.BackHandler
11+
import androidx.compose.foundation.clickable
12+
import androidx.compose.foundation.layout.Column
13+
import androidx.compose.foundation.layout.consumeWindowInsets
14+
import androidx.compose.foundation.layout.fillMaxWidth
15+
import androidx.compose.foundation.layout.padding
16+
import androidx.compose.foundation.lazy.LazyColumn
17+
import androidx.compose.foundation.lazy.items
18+
import androidx.compose.material3.ExperimentalMaterial3Api
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.tooling.preview.PreviewParameter
22+
import androidx.compose.ui.unit.dp
23+
import io.element.android.libraries.designsystem.components.button.BackButton
24+
import io.element.android.libraries.designsystem.preview.ElementPreview
25+
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
26+
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
27+
import io.element.android.libraries.designsystem.theme.components.Scaffold
28+
import io.element.android.libraries.designsystem.theme.components.TopAppBar
29+
import io.element.android.libraries.matrix.api.core.SessionId
30+
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
31+
32+
@Suppress("MultipleEmitters") // False positive
33+
@OptIn(ExperimentalMaterial3Api::class)
34+
@Composable
35+
fun AccountSelectView(
36+
state: AccountSelectState,
37+
onAccountSelected: (SessionId) -> Unit,
38+
onDismiss: () -> Unit,
39+
modifier: Modifier = Modifier,
40+
) {
41+
BackHandler(onBack = { onDismiss() })
42+
Scaffold(
43+
modifier = modifier,
44+
topBar = {
45+
TopAppBar(
46+
// TODO i18n
47+
titleStr = "Select account",
48+
navigationIcon = {
49+
BackButton(onClick = { onDismiss() })
50+
},
51+
)
52+
}
53+
) { paddingValues ->
54+
Column(
55+
Modifier
56+
.padding(paddingValues)
57+
.consumeWindowInsets(paddingValues)
58+
) {
59+
LazyColumn {
60+
items(state.accounts, key = { it.userId }) { matrixUser ->
61+
Column {
62+
MatrixUserRow(
63+
modifier = Modifier
64+
.fillMaxWidth()
65+
.clickable {
66+
onAccountSelected(matrixUser.userId)
67+
}
68+
.padding(vertical = 8.dp),
69+
matrixUser = matrixUser,
70+
)
71+
HorizontalDivider()
72+
}
73+
}
74+
}
75+
}
76+
}
77+
}
78+
79+
@PreviewsDayNight
80+
@Composable
81+
internal fun AccountSelectViewPreview(@PreviewParameter(AccountSelectStateProvider::class) state: AccountSelectState) = ElementPreview {
82+
AccountSelectView(
83+
state = state,
84+
onAccountSelected = {},
85+
onDismiss = {},
86+
)
87+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2025 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
5+
* Please see LICENSE files in the repository root for full details.
6+
*/
7+
8+
package io.element.android.libraries.accountselect.impl
9+
10+
import com.bumble.appyx.core.modality.BuildContext
11+
import com.bumble.appyx.core.node.Node
12+
import com.bumble.appyx.core.plugin.Plugin
13+
import com.squareup.anvil.annotations.ContributesBinding
14+
import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint
15+
import io.element.android.libraries.architecture.createNode
16+
import io.element.android.libraries.di.AppScope
17+
import javax.inject.Inject
18+
19+
@ContributesBinding(AppScope::class)
20+
class DefaultAccountSelectEntryPoint @Inject constructor() : AccountSelectEntryPoint {
21+
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): AccountSelectEntryPoint.NodeBuilder {
22+
val plugins = ArrayList<Plugin>()
23+
24+
return object : AccountSelectEntryPoint.NodeBuilder {
25+
override fun callback(callback: AccountSelectEntryPoint.Callback): AccountSelectEntryPoint.NodeBuilder {
26+
plugins += callback
27+
return this
28+
}
29+
30+
override fun build(): Node {
31+
return parentNode.createNode<AccountSelectNode>(buildContext, plugins)
32+
}
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)