Skip to content

Commit cf2c90e

Browse files
authored
Merge pull request #3467 from element-hq/feature/bma/accountCreation
Temporary account creation using Element Web.
2 parents d310c96 + 7f1d9bb commit cf2c90e

File tree

54 files changed

+1203
-29
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1203
-29
lines changed

appconfig/src/main/kotlin/io/element/android/appconfig/OnBoardingConfig.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ object OnBoardingConfig {
1212
const val CAN_LOGIN_WITH_QR_CODE = true
1313

1414
/** Whether the user can create an account using the app. */
15-
const val CAN_CREATE_ACCOUNT = false
15+
const val CAN_CREATE_ACCOUNT = true
1616
}

features/login/impl/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ dependencies {
4444
implementation(projects.libraries.oidc.api)
4545
implementation(libs.androidx.browser)
4646
implementation(platform(libs.network.retrofit.bom))
47+
implementation(libs.androidx.webkit)
4748
implementation(libs.network.retrofit)
4849
implementation(libs.serialization.json)
4950
api(projects.features.login.api)

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderDat
3030
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
3131
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
3232
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
33+
import io.element.android.features.login.impl.screens.createaccount.CreateAccountNode
3334
import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode
3435
import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode
3536
import io.element.android.libraries.architecture.BackstackView
@@ -109,6 +110,9 @@ class LoginFlowNode @AssistedInject constructor(
109110
@Parcelize
110111
data object LoginPassword : NavTarget
111112

113+
@Parcelize
114+
data class CreateAccount(val url: String) : NavTarget
115+
112116
@Parcelize
113117
data class OidcView(val oidcDetails: OidcDetails) : NavTarget
114118
}
@@ -140,6 +144,10 @@ class LoginFlowNode @AssistedInject constructor(
140144
}
141145
}
142146

147+
override fun onCreateAccountContinue(url: String) {
148+
backstack.push(NavTarget.CreateAccount(url))
149+
}
150+
143151
override fun onLoginPasswordNeeded() {
144152
backstack.push(NavTarget.LoginPassword)
145153
}
@@ -180,6 +188,12 @@ class LoginFlowNode @AssistedInject constructor(
180188
is NavTarget.OidcView -> {
181189
oidcEntryPoint.createFallbackWebViewNode(this, buildContext, navTarget.oidcDetails.url)
182190
}
191+
is NavTarget.CreateAccount -> {
192+
val inputs = CreateAccountNode.Inputs(
193+
url = navTarget.url,
194+
)
195+
createNode<CreateAccountNode>(buildContext, listOf(inputs))
196+
}
183197
}
184198
}
185199

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2023, 2024 New Vector Ltd.
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
* Please see LICENSE in the repository root for full details.
6+
*/
7+
8+
package io.element.android.features.login.impl.resolver.network
9+
10+
import kotlinx.serialization.SerialName
11+
import kotlinx.serialization.Serializable
12+
13+
/**
14+
* Example:
15+
* <pre>
16+
* {
17+
* "registration_helper_url": "https://element.io"
18+
* }
19+
* </pre>
20+
* .
21+
*/
22+
@Serializable
23+
data class ElementWellKnown(
24+
@SerialName("registration_helper_url")
25+
val registrationHelperUrl: String? = null,
26+
)

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/resolver/network/WellknownAPI.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ import retrofit2.http.GET
1212
internal interface WellknownAPI {
1313
@GET(".well-known/matrix/client")
1414
suspend fun getWellKnown(): WellKnown
15+
16+
@GET(".well-known/element/element.json")
17+
suspend fun getElementWellKnown(): ElementWellKnown
1518
}

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class ConfirmAccountProviderNode @AssistedInject constructor(
4343
interface Callback : Plugin {
4444
fun onLoginPasswordNeeded()
4545
fun onOidcDetails(oidcDetails: OidcDetails)
46+
fun onCreateAccountContinue(url: String)
4647
fun onChangeAccountProvider()
4748
}
4849

@@ -54,6 +55,10 @@ class ConfirmAccountProviderNode @AssistedInject constructor(
5455
plugins<Callback>().forEach { it.onLoginPasswordNeeded() }
5556
}
5657

58+
private fun onCreateAccountContinue(url: String) {
59+
plugins<Callback>().forEach { it.onCreateAccountContinue(url) }
60+
}
61+
5762
private fun onChangeAccountProvider() {
5863
plugins<Callback>().forEach { it.onChangeAccountProvider() }
5964
}
@@ -67,6 +72,7 @@ class ConfirmAccountProviderNode @AssistedInject constructor(
6772
modifier = modifier,
6873
onOidcDetails = ::onOidcDetails,
6974
onNeedLoginPassword = ::onLoginPasswordNeeded,
75+
onCreateAccountContinue = ::onCreateAccountContinue,
7076
onChange = ::onChangeAccountProvider,
7177
onLearnMoreClick = { openLearnMorePage(context) },
7278
)

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenter.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import dagger.assisted.AssistedInject
2121
import io.element.android.features.login.impl.DefaultLoginUserStory
2222
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
2323
import io.element.android.features.login.impl.error.ChangeServerError
24+
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
25+
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
2426
import io.element.android.libraries.architecture.AsyncData
2527
import io.element.android.libraries.architecture.Presenter
2628
import io.element.android.libraries.architecture.runCatchingUpdatingState
@@ -36,6 +38,7 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
3638
private val authenticationService: MatrixAuthenticationService,
3739
private val oidcActionFlow: OidcActionFlow,
3840
private val defaultLoginUserStory: DefaultLoginUserStory,
41+
private val webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever,
3942
) : Presenter<ConfirmAccountProviderState> {
4043
data class Params(
4144
val isAccountCreation: Boolean,
@@ -90,13 +93,24 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
9093
if (matrixHomeServerDetails.supportsOidcLogin) {
9194
// Retrieve the details right now
9295
LoginFlow.OidcFlow(authenticationService.getOidcUrl().getOrThrow())
96+
} else if (params.isAccountCreation) {
97+
val url = webClientUrlForAuthenticationRetriever.retrieve(homeserverUrl)
98+
LoginFlow.AccountCreationFlow(url)
9399
} else if (matrixHomeServerDetails.supportsPasswordLogin) {
94100
LoginFlow.PasswordLogin
95101
} else {
96102
error("Unsupported login flow")
97103
}
98104
}.getOrThrow()
99-
}.runCatchingUpdatingState(loginFlowAction, errorTransform = ChangeServerError::from)
105+
}.runCatchingUpdatingState(
106+
state = loginFlowAction,
107+
errorTransform = {
108+
when (it) {
109+
is AccountCreationNotSupported -> it
110+
else -> ChangeServerError.from(it)
111+
}
112+
}
113+
)
100114
}
101115

102116
private suspend fun onOidcAction(

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderState.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ data class ConfirmAccountProviderState(
2424
sealed interface LoginFlow {
2525
data object PasswordLogin : LoginFlow
2626
data class OidcFlow(val oidcDetails: OidcDetails) : LoginFlow
27+
data class AccountCreationFlow(val url: String) : LoginFlow
2728
}

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderStateProvider.kt

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,33 @@
88
package io.element.android.features.login.impl.screens.confirmaccountprovider
99

1010
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
11+
import io.element.android.features.login.impl.accountprovider.AccountProvider
1112
import io.element.android.features.login.impl.accountprovider.anAccountProvider
13+
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
1214
import io.element.android.libraries.architecture.AsyncData
1315

1416
open class ConfirmAccountProviderStateProvider : PreviewParameterProvider<ConfirmAccountProviderState> {
1517
override val values: Sequence<ConfirmAccountProviderState>
1618
get() = sequenceOf(
1719
aConfirmAccountProviderState(),
18-
// Add other state here
20+
aConfirmAccountProviderState(
21+
isAccountCreation = true,
22+
),
23+
aConfirmAccountProviderState(
24+
isAccountCreation = true,
25+
loginFlow = AsyncData.Failure(AccountCreationNotSupported())
26+
),
1927
)
2028
}
2129

22-
fun aConfirmAccountProviderState() = ConfirmAccountProviderState(
23-
accountProvider = anAccountProvider(),
24-
isAccountCreation = false,
25-
loginFlow = AsyncData.Uninitialized,
26-
eventSink = {}
30+
private fun aConfirmAccountProviderState(
31+
accountProvider: AccountProvider = anAccountProvider(),
32+
isAccountCreation: Boolean = false,
33+
loginFlow: AsyncData<LoginFlow> = AsyncData.Uninitialized,
34+
eventSink: (ConfirmAccountProviderEvents) -> Unit = {},
35+
) = ConfirmAccountProviderState(
36+
accountProvider = accountProvider,
37+
isAccountCreation = isAccountCreation,
38+
loginFlow = loginFlow,
39+
eventSink = eventSink
2740
)

features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp
2222
import io.element.android.features.login.impl.R
2323
import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
2424
import io.element.android.features.login.impl.error.ChangeServerError
25+
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
2526
import io.element.android.libraries.architecture.AsyncData
2627
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
2728
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
@@ -42,6 +43,7 @@ fun ConfirmAccountProviderView(
4243
onOidcDetails: (OidcDetails) -> Unit,
4344
onNeedLoginPassword: () -> Unit,
4445
onLearnMoreClick: () -> Unit,
46+
onCreateAccountContinue: (url: String) -> Unit,
4547
onChange: () -> Unit,
4648
modifier: Modifier = Modifier,
4749
) {
@@ -109,12 +111,23 @@ fun ConfirmAccountProviderView(
109111
)
110112
}
111113
is ChangeServerError.SlidingSyncAlert -> {
112-
SlidingSyncNotSupportedDialog(onLearnMoreClick = {
113-
onLearnMoreClick()
114-
eventSink(ConfirmAccountProviderEvents.ClearError)
115-
}, onDismiss = {
116-
eventSink(ConfirmAccountProviderEvents.ClearError)
117-
})
114+
SlidingSyncNotSupportedDialog(
115+
onLearnMoreClick = {
116+
onLearnMoreClick()
117+
eventSink(ConfirmAccountProviderEvents.ClearError)
118+
},
119+
onDismiss = {
120+
eventSink(ConfirmAccountProviderEvents.ClearError)
121+
}
122+
)
123+
}
124+
is AccountCreationNotSupported -> {
125+
ErrorDialog(
126+
content = stringResource(CommonStrings.error_account_creation_not_possible),
127+
onSubmit = {
128+
eventSink.invoke(ConfirmAccountProviderEvents.ClearError)
129+
}
130+
)
118131
}
119132
}
120133
}
@@ -123,6 +136,7 @@ fun ConfirmAccountProviderView(
123136
when (val loginFlowState = state.loginFlow.data) {
124137
is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails)
125138
LoginFlow.PasswordLogin -> onNeedLoginPassword()
139+
is LoginFlow.AccountCreationFlow -> onCreateAccountContinue(loginFlowState.url)
126140
}
127141
}
128142
AsyncData.Uninitialized -> Unit
@@ -139,6 +153,7 @@ internal fun ConfirmAccountProviderViewPreview(
139153
state = state,
140154
onOidcDetails = {},
141155
onNeedLoginPassword = {},
156+
onCreateAccountContinue = {},
142157
onLearnMoreClick = {},
143158
onChange = {},
144159
)

0 commit comments

Comments
 (0)