mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Remove the legacy web-based account creation for matrix.org
This was added as a temporary measure to allow matrix.org users to use Element X before it supported MAS, but it hasn't been needed for a very long time
This commit is contained in:
committed by
Jorge Martin Espinosa
parent
76092fcba6
commit
7c648b34b7
-26
@@ -37,7 +37,6 @@ import io.element.android.features.login.impl.screens.changeaccountprovider.Chan
|
|||||||
import io.element.android.features.login.impl.screens.chooseaccountprovider.ChooseAccountProviderNode
|
import io.element.android.features.login.impl.screens.chooseaccountprovider.ChooseAccountProviderNode
|
||||||
import io.element.android.features.login.impl.screens.classic.ClassicFlowNode
|
import io.element.android.features.login.impl.screens.classic.ClassicFlowNode
|
||||||
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
|
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
|
||||||
import io.element.android.features.login.impl.screens.createaccount.CreateAccountNode
|
|
||||||
import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode
|
import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode
|
||||||
import io.element.android.features.login.impl.screens.onboarding.OnBoardingNode
|
import io.element.android.features.login.impl.screens.onboarding.OnBoardingNode
|
||||||
import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode
|
import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode
|
||||||
@@ -140,9 +139,6 @@ class LoginFlowNode(
|
|||||||
data class LoginPassword(
|
data class LoginPassword(
|
||||||
val initialLogin: String = "",
|
val initialLogin: String = "",
|
||||||
) : NavTarget
|
) : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class CreateAccount(val url: String) : NavTarget
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
@@ -164,10 +160,6 @@ class LoginFlowNode(
|
|||||||
override fun navigateToOAuth(oAuthDetails: OAuthDetails) {
|
override fun navigateToOAuth(oAuthDetails: OAuthDetails) {
|
||||||
navigateToMas(oAuthDetails)
|
navigateToMas(oAuthDetails)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToCreateAccount(url: String) {
|
|
||||||
backstack.push(NavTarget.CreateAccount(url))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
createNode<ClassicFlowNode>(buildContext, listOf(callback))
|
createNode<ClassicFlowNode>(buildContext, listOf(callback))
|
||||||
}
|
}
|
||||||
@@ -201,10 +193,6 @@ class LoginFlowNode(
|
|||||||
navigateToMas(oAuthDetails)
|
navigateToMas(oAuthDetails)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToCreateAccount(url: String) {
|
|
||||||
backstack.push(NavTarget.CreateAccount(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun navigateToDeveloperSettings() {
|
override fun navigateToDeveloperSettings() {
|
||||||
backstack.push(NavTarget.AppDeveloperSettings)
|
backstack.push(NavTarget.AppDeveloperSettings)
|
||||||
}
|
}
|
||||||
@@ -247,10 +235,6 @@ class LoginFlowNode(
|
|||||||
navigateToMas(oAuthDetails)
|
navigateToMas(oAuthDetails)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToCreateAccount(url: String) {
|
|
||||||
backstack.push(NavTarget.CreateAccount(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun navigateToLoginPassword() {
|
override fun navigateToLoginPassword() {
|
||||||
backstack.push(NavTarget.LoginPassword())
|
backstack.push(NavTarget.LoginPassword())
|
||||||
}
|
}
|
||||||
@@ -274,10 +258,6 @@ class LoginFlowNode(
|
|||||||
navigateToMas(oAuthDetails)
|
navigateToMas(oAuthDetails)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToCreateAccount(url: String) {
|
|
||||||
backstack.push(NavTarget.CreateAccount(url))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun navigateToLoginPassword() {
|
override fun navigateToLoginPassword() {
|
||||||
backstack.push(NavTarget.LoginPassword())
|
backstack.push(NavTarget.LoginPassword())
|
||||||
}
|
}
|
||||||
@@ -324,12 +304,6 @@ class LoginFlowNode(
|
|||||||
)
|
)
|
||||||
createNode<LoginPasswordNode>(buildContext, plugins = listOf(inputs))
|
createNode<LoginPasswordNode>(buildContext, plugins = listOf(inputs))
|
||||||
}
|
}
|
||||||
is NavTarget.CreateAccount -> {
|
|
||||||
val inputs = CreateAccountNode.Inputs(
|
|
||||||
url = navTarget.url,
|
|
||||||
)
|
|
||||||
createNode<CreateAccountNode>(buildContext, listOf(inputs))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-6
@@ -20,7 +20,6 @@ import io.element.android.features.login.impl.screens.classic.loginwithclassic.L
|
|||||||
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderPresenter
|
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderPresenter
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
import io.element.android.features.login.impl.screens.onboarding.OnBoardingPresenter
|
import io.element.android.features.login.impl.screens.onboarding.OnBoardingPresenter
|
||||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||||
@@ -39,7 +38,6 @@ import io.element.android.libraries.oauth.api.OAuthActionFlow
|
|||||||
class LoginModePresenter(
|
class LoginModePresenter(
|
||||||
private val oAuthActionFlow: OAuthActionFlow,
|
private val oAuthActionFlow: OAuthActionFlow,
|
||||||
private val authenticationService: MatrixAuthenticationService,
|
private val authenticationService: MatrixAuthenticationService,
|
||||||
private val webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever,
|
|
||||||
private val localNetworkPermissionGate: LocalNetworkPermissionGate,
|
private val localNetworkPermissionGate: LocalNetworkPermissionGate,
|
||||||
) : Presenter<LoginModeState> {
|
) : Presenter<LoginModeState> {
|
||||||
@Composable
|
@Composable
|
||||||
@@ -90,11 +88,9 @@ class LoginModePresenter(
|
|||||||
authenticationService.getOAuthUrl(prompt = oAuthPrompt, loginHint = request.loginHint).getOrThrow()
|
authenticationService.getOAuthUrl(prompt = oAuthPrompt, loginHint = request.loginHint).getOrThrow()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
request.isAccountCreation -> LoginMode.AccountCreation(
|
request.isAccountCreation -> throw AccountCreationNotSupported()
|
||||||
webClientUrlForAuthenticationRetriever.retrieve(request.homeserverUrl)
|
|
||||||
)
|
|
||||||
matrixHomeServerDetails.supportsPasswordLogin -> LoginMode.PasswordLogin
|
matrixHomeServerDetails.supportsPasswordLogin -> LoginMode.PasswordLogin
|
||||||
else -> error("Unsupported login flow")
|
else -> error("Unsupported authentication flow")
|
||||||
}
|
}
|
||||||
}.getOrThrow()
|
}.getOrThrow()
|
||||||
}.runCatchingUpdatingState(
|
}.runCatchingUpdatingState(
|
||||||
|
|||||||
-1
@@ -20,5 +20,4 @@ data class LoginModeState(
|
|||||||
sealed interface LoginMode {
|
sealed interface LoginMode {
|
||||||
data object PasswordLogin : LoginMode
|
data object PasswordLogin : LoginMode
|
||||||
data class OAuth(val oAuthDetails: OAuthDetails) : LoginMode
|
data class OAuth(val oAuthDetails: OAuthDetails) : LoginMode
|
||||||
data class AccountCreation(val url: String) : LoginMode
|
|
||||||
}
|
}
|
||||||
|
|||||||
-3
@@ -34,7 +34,6 @@ fun LoginModeView(
|
|||||||
onLearnMoreClick: () -> Unit,
|
onLearnMoreClick: () -> Unit,
|
||||||
onOAuthDetails: (OAuthDetails) -> Unit,
|
onOAuthDetails: (OAuthDetails) -> Unit,
|
||||||
onNeedLoginPassword: () -> Unit,
|
onNeedLoginPassword: () -> Unit,
|
||||||
onCreateAccountContinue: (url: String) -> Unit
|
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
when (loginMode) {
|
when (loginMode) {
|
||||||
@@ -120,7 +119,6 @@ fun LoginModeView(
|
|||||||
when (val loginModeData = loginMode.data) {
|
when (val loginModeData = loginMode.data) {
|
||||||
is LoginMode.OAuth -> onOAuthDetails(loginModeData.oAuthDetails)
|
is LoginMode.OAuth -> onOAuthDetails(loginModeData.oAuthDetails)
|
||||||
LoginMode.PasswordLogin -> onNeedLoginPassword()
|
LoginMode.PasswordLogin -> onNeedLoginPassword()
|
||||||
is LoginMode.AccountCreation -> onCreateAccountContinue(loginModeData.url)
|
|
||||||
}
|
}
|
||||||
// Also clear the data, to let the next screen be able to go back
|
// Also clear the data, to let the next screen be able to go back
|
||||||
onClearError()
|
onClearError()
|
||||||
@@ -139,7 +137,6 @@ internal fun LoginModeViewPreview(@PreviewParameter(LoginModeViewErrorProvider::
|
|||||||
onLearnMoreClick = {},
|
onLearnMoreClick = {},
|
||||||
onOAuthDetails = {},
|
onOAuthDetails = {},
|
||||||
onNeedLoginPassword = {},
|
onNeedLoginPassword = {},
|
||||||
onCreateAccountContinue = {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -32,7 +32,6 @@ class ChooseAccountProviderNode(
|
|||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToLoginPassword()
|
fun navigateToLoginPassword()
|
||||||
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
||||||
fun navigateToCreateAccount(url: String)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback: Callback = callback()
|
private val callback: Callback = callback()
|
||||||
@@ -48,7 +47,6 @@ class ChooseAccountProviderNode(
|
|||||||
onOAuthDetails = callback::navigateToOAuth,
|
onOAuthDetails = callback::navigateToOAuth,
|
||||||
onNeedLoginPassword = callback::navigateToLoginPassword,
|
onNeedLoginPassword = callback::navigateToLoginPassword,
|
||||||
onLearnMoreClick = { openLearnMorePage(context) },
|
onLearnMoreClick = { openLearnMorePage(context) },
|
||||||
onCreateAccountContinue = callback::navigateToCreateAccount,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-3
@@ -57,7 +57,6 @@ fun ChooseAccountProviderView(
|
|||||||
onOAuthDetails: (OAuthDetails) -> Unit,
|
onOAuthDetails: (OAuthDetails) -> Unit,
|
||||||
onNeedLoginPassword: () -> Unit,
|
onNeedLoginPassword: () -> Unit,
|
||||||
onLearnMoreClick: () -> Unit,
|
onLearnMoreClick: () -> Unit,
|
||||||
onCreateAccountContinue: (url: String) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val isLoading by remember(state.loginModeState.loginMode) {
|
val isLoading by remember(state.loginModeState.loginMode) {
|
||||||
@@ -137,7 +136,6 @@ fun ChooseAccountProviderView(
|
|||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onOAuthDetails = onOAuthDetails,
|
onOAuthDetails = onOAuthDetails,
|
||||||
onNeedLoginPassword = onNeedLoginPassword,
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
onCreateAccountContinue = onCreateAccountContinue,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,6 +159,5 @@ internal fun ChooseAccountProviderViewPreview(@PreviewParameter(ChooseAccountPro
|
|||||||
onLearnMoreClick = { },
|
onLearnMoreClick = { },
|
||||||
onOAuthDetails = { },
|
onOAuthDetails = { },
|
||||||
onNeedLoginPassword = { },
|
onNeedLoginPassword = { },
|
||||||
onCreateAccountContinue = { },
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
-5
@@ -55,7 +55,6 @@ class ClassicFlowNode(
|
|||||||
fun navigateToOnBoarding(allowBackNavigation: Boolean)
|
fun navigateToOnBoarding(allowBackNavigation: Boolean)
|
||||||
fun navigateToLoginPassword()
|
fun navigateToLoginPassword()
|
||||||
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
||||||
fun navigateToCreateAccount(url: String)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface NavTarget : Parcelable {
|
sealed interface NavTarget : Parcelable {
|
||||||
@@ -115,10 +114,6 @@ class ClassicFlowNode(
|
|||||||
callback.navigateToOAuth(oAuthDetails)
|
callback.navigateToOAuth(oAuthDetails)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToCreateAccount(url: String) {
|
|
||||||
callback.navigateToCreateAccount(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun navigateToMissingKeyBackup() {
|
override fun navigateToMissingKeyBackup() {
|
||||||
backstack.push(NavTarget.MissingKeyBackup)
|
backstack.push(NavTarget.MissingKeyBackup)
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -36,7 +36,6 @@ class LoginWithClassicNode(
|
|||||||
fun navigateToOtherOptions()
|
fun navigateToOtherOptions()
|
||||||
fun navigateToLoginPassword()
|
fun navigateToLoginPassword()
|
||||||
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
||||||
fun navigateToCreateAccount(url: String)
|
|
||||||
fun navigateToMissingKeyBackup()
|
fun navigateToMissingKeyBackup()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +62,6 @@ class LoginWithClassicNode(
|
|||||||
onOAuthDetails = callback::navigateToOAuth,
|
onOAuthDetails = callback::navigateToOAuth,
|
||||||
onNeedLoginPassword = callback::navigateToLoginPassword,
|
onNeedLoginPassword = callback::navigateToLoginPassword,
|
||||||
onLearnMoreClick = { openLearnMorePage(context) },
|
onLearnMoreClick = { openLearnMorePage(context) },
|
||||||
onCreateAccountContinue = callback::navigateToCreateAccount,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-3
@@ -64,7 +64,6 @@ fun LoginWithClassicView(
|
|||||||
onOAuthDetails: (OAuthDetails) -> Unit,
|
onOAuthDetails: (OAuthDetails) -> Unit,
|
||||||
onNeedLoginPassword: () -> Unit,
|
onNeedLoginPassword: () -> Unit,
|
||||||
onLearnMoreClick: () -> Unit,
|
onLearnMoreClick: () -> Unit,
|
||||||
onCreateAccountContinue: (url: String) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val isLoading by remember(state.loginModeState.loginMode) {
|
val isLoading by remember(state.loginModeState.loginMode) {
|
||||||
@@ -204,7 +203,6 @@ fun LoginWithClassicView(
|
|||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onOAuthDetails = onOAuthDetails,
|
onOAuthDetails = onOAuthDetails,
|
||||||
onNeedLoginPassword = onNeedLoginPassword,
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
onCreateAccountContinue = onCreateAccountContinue,
|
|
||||||
)
|
)
|
||||||
LocalNetworkPermissionDialogView(
|
LocalNetworkPermissionDialogView(
|
||||||
dialog = state.loginModeState.localNetworkPermissionDialog,
|
dialog = state.loginModeState.localNetworkPermissionDialog,
|
||||||
@@ -226,6 +224,5 @@ internal fun LoginWithClassicViewPreview(@PreviewParameter(LoginWithClassicState
|
|||||||
onOAuthDetails = {},
|
onOAuthDetails = {},
|
||||||
onNeedLoginPassword = {},
|
onNeedLoginPassword = {},
|
||||||
onLearnMoreClick = {},
|
onLearnMoreClick = {},
|
||||||
onCreateAccountContinue = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -45,7 +45,6 @@ class ConfirmAccountProviderNode(
|
|||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToLoginPassword()
|
fun navigateToLoginPassword()
|
||||||
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
||||||
fun navigateToCreateAccount(url: String)
|
|
||||||
fun navigateToChangeAccountProvider()
|
fun navigateToChangeAccountProvider()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +59,6 @@ class ConfirmAccountProviderNode(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
onOAuthDetails = callback::navigateToOAuth,
|
onOAuthDetails = callback::navigateToOAuth,
|
||||||
onNeedLoginPassword = callback::navigateToLoginPassword,
|
onNeedLoginPassword = callback::navigateToLoginPassword,
|
||||||
onCreateAccountContinue = callback::navigateToCreateAccount,
|
|
||||||
onChange = callback::navigateToChangeAccountProvider,
|
onChange = callback::navigateToChangeAccountProvider,
|
||||||
onLearnMoreClick = { openLearnMorePage(context) },
|
onLearnMoreClick = { openLearnMorePage(context) },
|
||||||
)
|
)
|
||||||
|
|||||||
-3
@@ -43,7 +43,6 @@ fun ConfirmAccountProviderView(
|
|||||||
onOAuthDetails: (OAuthDetails) -> Unit,
|
onOAuthDetails: (OAuthDetails) -> Unit,
|
||||||
onNeedLoginPassword: () -> Unit,
|
onNeedLoginPassword: () -> Unit,
|
||||||
onLearnMoreClick: () -> Unit,
|
onLearnMoreClick: () -> Unit,
|
||||||
onCreateAccountContinue: (url: String) -> Unit,
|
|
||||||
onChange: () -> Unit,
|
onChange: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@@ -107,7 +106,6 @@ fun ConfirmAccountProviderView(
|
|||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onOAuthDetails = onOAuthDetails,
|
onOAuthDetails = onOAuthDetails,
|
||||||
onNeedLoginPassword = onNeedLoginPassword,
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
onCreateAccountContinue = onCreateAccountContinue,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +129,6 @@ internal fun ConfirmAccountProviderViewPreview(
|
|||||||
state = state,
|
state = state,
|
||||||
onOAuthDetails = {},
|
onOAuthDetails = {},
|
||||||
onNeedLoginPassword = {},
|
onNeedLoginPassword = {},
|
||||||
onCreateAccountContinue = {},
|
|
||||||
onLearnMoreClick = {},
|
onLearnMoreClick = {},
|
||||||
onChange = {},
|
onChange = {},
|
||||||
)
|
)
|
||||||
|
|||||||
-14
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
sealed interface CreateAccountEvents {
|
|
||||||
data class SetPageProgress(val progress: Int) : CreateAccountEvents
|
|
||||||
data class OnMessageReceived(val message: String) : CreateAccountEvents
|
|
||||||
}
|
|
||||||
-58
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import androidx.activity.compose.LocalActivity
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import com.bumble.appyx.core.modality.BuildContext
|
|
||||||
import com.bumble.appyx.core.node.Node
|
|
||||||
import com.bumble.appyx.core.plugin.Plugin
|
|
||||||
import dev.zacsweers.metro.AppScope
|
|
||||||
import dev.zacsweers.metro.Assisted
|
|
||||||
import dev.zacsweers.metro.AssistedInject
|
|
||||||
import io.element.android.annotations.ContributesNode
|
|
||||||
import io.element.android.compound.theme.ElementTheme
|
|
||||||
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
|
||||||
import io.element.android.libraries.architecture.NodeInputs
|
|
||||||
import io.element.android.libraries.architecture.inputs
|
|
||||||
|
|
||||||
@ContributesNode(AppScope::class)
|
|
||||||
@AssistedInject
|
|
||||||
class CreateAccountNode(
|
|
||||||
@Assisted buildContext: BuildContext,
|
|
||||||
@Assisted plugins: List<Plugin>,
|
|
||||||
presenterFactory: CreateAccountPresenter.Factory,
|
|
||||||
) : Node(buildContext, plugins = plugins) {
|
|
||||||
data class Inputs(
|
|
||||||
val url: String,
|
|
||||||
) : NodeInputs
|
|
||||||
|
|
||||||
private val presenter = presenterFactory.create(inputs<Inputs>().url)
|
|
||||||
|
|
||||||
private fun onOpenExternalUrl(activity: Activity, darkTheme: Boolean, url: String) {
|
|
||||||
activity.openUrlInChromeCustomTab(null, darkTheme, url)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun View(modifier: Modifier) {
|
|
||||||
val activity = requireNotNull(LocalActivity.current)
|
|
||||||
val isDark = ElementTheme.isLightTheme.not()
|
|
||||||
val state = presenter.present()
|
|
||||||
CreateAccountView(
|
|
||||||
state = state,
|
|
||||||
modifier = modifier,
|
|
||||||
onBackClick = ::navigateUp,
|
|
||||||
onOpenExternalUrl = {
|
|
||||||
onOpenExternalUrl(activity, isDark, it)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-82
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2023-2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.MutableState
|
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import dev.zacsweers.metro.Assisted
|
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
|
||||||
import dev.zacsweers.metro.AssistedInject
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
|
||||||
import io.element.android.libraries.architecture.Presenter
|
|
||||||
import io.element.android.libraries.core.extensions.flatMap
|
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@AssistedInject
|
|
||||||
class CreateAccountPresenter(
|
|
||||||
@Assisted private val url: String,
|
|
||||||
private val authenticationService: MatrixAuthenticationService,
|
|
||||||
private val messageParser: MessageParser,
|
|
||||||
private val buildMeta: BuildMeta,
|
|
||||||
) : Presenter<CreateAccountState> {
|
|
||||||
@AssistedFactory
|
|
||||||
interface Factory {
|
|
||||||
fun create(url: String): CreateAccountPresenter
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun present(): CreateAccountState {
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
val pageProgress: MutableState<Int> = remember { mutableIntStateOf(0) }
|
|
||||||
val createAction: MutableState<AsyncAction<SessionId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
|
||||||
|
|
||||||
fun handleEvent(event: CreateAccountEvents) {
|
|
||||||
when (event) {
|
|
||||||
is CreateAccountEvents.SetPageProgress -> {
|
|
||||||
pageProgress.value = event.progress
|
|
||||||
}
|
|
||||||
is CreateAccountEvents.OnMessageReceived -> {
|
|
||||||
// Ignore unexpected message
|
|
||||||
if (event.message.contains("isTrusted")) return
|
|
||||||
coroutineScope.importSession(event.message, createAction)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CreateAccountState(
|
|
||||||
url = url,
|
|
||||||
pageProgress = pageProgress.value,
|
|
||||||
isDebugBuild = buildMeta.isDebuggable,
|
|
||||||
createAction = createAction.value,
|
|
||||||
eventSink = ::handleEvent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CoroutineScope.importSession(message: String, loggedInState: MutableState<AsyncAction<SessionId>>) = launch {
|
|
||||||
loggedInState.value = AsyncAction.Loading
|
|
||||||
runCatchingExceptions {
|
|
||||||
messageParser.parse(message)
|
|
||||||
}.flatMap { externalSession ->
|
|
||||||
authenticationService.importCreatedSession(externalSession)
|
|
||||||
}.onSuccess { sessionId ->
|
|
||||||
loggedInState.value = AsyncAction.Success(sessionId)
|
|
||||||
}.onFailure { failure ->
|
|
||||||
loggedInState.value = AsyncAction.Failure(failure)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
|
||||||
|
|
||||||
data class CreateAccountState(
|
|
||||||
val url: String,
|
|
||||||
val pageProgress: Int,
|
|
||||||
val createAction: AsyncAction<SessionId>,
|
|
||||||
val isDebugBuild: Boolean,
|
|
||||||
val eventSink: (CreateAccountEvents) -> Unit
|
|
||||||
)
|
|
||||||
-34
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2023-2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
|
||||||
|
|
||||||
open class CreateAccountStateProvider : PreviewParameterProvider<CreateAccountState> {
|
|
||||||
override val values: Sequence<CreateAccountState>
|
|
||||||
get() = sequenceOf(
|
|
||||||
aCreateAccountState(),
|
|
||||||
aCreateAccountState(pageProgress = 33),
|
|
||||||
aCreateAccountState(createAction = AsyncAction.Loading),
|
|
||||||
aCreateAccountState(createAction = AsyncAction.Failure(RuntimeException("Failed to create account"))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun aCreateAccountState(
|
|
||||||
pageProgress: Int = 100,
|
|
||||||
createAction: AsyncAction<SessionId> = AsyncAction.Uninitialized,
|
|
||||||
) = CreateAccountState(
|
|
||||||
url = "https://example.com",
|
|
||||||
isDebugBuild = true,
|
|
||||||
pageProgress = pageProgress,
|
|
||||||
createAction = createAction,
|
|
||||||
eventSink = {}
|
|
||||||
)
|
|
||||||
-176
@@ -1,176 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.webkit.JsResult
|
|
||||||
import android.webkit.WebChromeClient
|
|
||||||
import android.webkit.WebView
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.fadeIn
|
|
||||||
import androidx.compose.animation.fadeOut
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
|
||||||
import io.element.android.compound.theme.ElementTheme
|
|
||||||
import io.element.android.features.login.impl.R
|
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
|
||||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
|
||||||
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun CreateAccountView(
|
|
||||||
state: CreateAccountState,
|
|
||||||
onBackClick: () -> Unit,
|
|
||||||
onOpenExternalUrl: (String) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
modifier = modifier,
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
titleStr = stringResource(R.string.screen_create_account_title),
|
|
||||||
navigationIcon = {
|
|
||||||
BackButton(onClick = onBackClick)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { contentPadding ->
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(contentPadding)
|
|
||||||
.consumeWindowInsets(contentPadding)
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
CreateAccountWebView(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize(),
|
|
||||||
state = state,
|
|
||||||
onWebViewCreate = { webView ->
|
|
||||||
WebViewMessageInterceptor(
|
|
||||||
webView,
|
|
||||||
state.isDebugBuild,
|
|
||||||
onOpenExternalUrl = onOpenExternalUrl,
|
|
||||||
onMessage = {
|
|
||||||
state.eventSink(CreateAccountEvents.OnMessageReceived(it))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = state.pageProgress != 100,
|
|
||||||
// Disable enter animation
|
|
||||||
enter = fadeIn(initialAlpha = 1f),
|
|
||||||
exit = fadeOut(),
|
|
||||||
) {
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(2.dp),
|
|
||||||
progress = { state.pageProgress / 100f },
|
|
||||||
trackColor = ElementTheme.colors.progressIndicatorTrackColor,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncActionView(
|
|
||||||
async = state.createAction,
|
|
||||||
onSuccess = {},
|
|
||||||
onErrorDismiss = onBackClick,
|
|
||||||
onRetry = null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun CreateAccountWebView(
|
|
||||||
state: CreateAccountState,
|
|
||||||
onWebViewCreate: (WebView) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
if (LocalInspectionMode.current) {
|
|
||||||
Box(modifier = modifier, contentAlignment = Alignment.Center) {
|
|
||||||
Text("WebView - can't be previewed")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
AndroidView(
|
|
||||||
modifier = modifier,
|
|
||||||
factory = { context ->
|
|
||||||
WebView(context).apply {
|
|
||||||
onWebViewCreate(this)
|
|
||||||
setup(state)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update = { webView ->
|
|
||||||
if (webView.url != state.url) {
|
|
||||||
webView.loadUrl(state.url)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRelease = { webView ->
|
|
||||||
webView.destroy()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SetJavaScriptEnabled")
|
|
||||||
private fun WebView.setup(state: CreateAccountState) {
|
|
||||||
layoutParams = ViewGroup.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
)
|
|
||||||
with(settings) {
|
|
||||||
javaScriptEnabled = true
|
|
||||||
domStorageEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
webChromeClient = object : WebChromeClient() {
|
|
||||||
override fun onProgressChanged(view: WebView?, newProgress: Int) {
|
|
||||||
super.onProgressChanged(view, newProgress)
|
|
||||||
state.eventSink(CreateAccountEvents.SetPageProgress(newProgress))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onJsBeforeUnload(view: WebView?, url: String?, message: String?, result: JsResult?): Boolean {
|
|
||||||
Timber.w("onJsBeforeUnload, cancelling the dialog, we will open external links in a Custom Chrome Tab")
|
|
||||||
result?.confirm()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun CreateAccountViewPreview(@PreviewParameter(CreateAccountStateProvider::class) state: CreateAccountState) = ElementPreview {
|
|
||||||
CreateAccountView(
|
|
||||||
state = state,
|
|
||||||
onBackClick = {},
|
|
||||||
onOpenExternalUrl = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import dev.zacsweers.metro.AppScope
|
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
|
||||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
|
||||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
|
||||||
|
|
||||||
interface MessageParser {
|
|
||||||
/**
|
|
||||||
* Parse the message and return the ExternalSession object, or
|
|
||||||
* throw an exception if the message is invalid.
|
|
||||||
*/
|
|
||||||
fun parse(message: String): ExternalSession
|
|
||||||
}
|
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
|
||||||
class DefaultMessageParser(
|
|
||||||
private val accountProviderDataSource: AccountProviderDataSource,
|
|
||||||
private val json: JsonProvider,
|
|
||||||
) : MessageParser {
|
|
||||||
override fun parse(message: String): ExternalSession {
|
|
||||||
val response = json().decodeFromString(MobileRegistrationResponse.serializer(), message)
|
|
||||||
val userId = response.userId ?: error("No user ID in response")
|
|
||||||
val homeServer = response.homeServer ?: accountProviderDataSource.flow.value.url
|
|
||||||
val accessToken = response.accessToken ?: error("No access token in response")
|
|
||||||
val deviceId = response.deviceId ?: error("No device ID in response")
|
|
||||||
return ExternalSession(
|
|
||||||
userId = userId,
|
|
||||||
homeserverUrl = homeServer,
|
|
||||||
accessToken = accessToken,
|
|
||||||
deviceId = deviceId,
|
|
||||||
refreshToken = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-28
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For ref:
|
|
||||||
* https://github.com/element-hq/matrix-react-sdk/pull/42/files#diff-2bbba5a742004fd4e924a639ded444279f66f7ad890cb669fbc91ac6b8638c64R56
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class MobileRegistrationResponse(
|
|
||||||
@SerialName("user_id")
|
|
||||||
val userId: String? = null,
|
|
||||||
@SerialName("home_server")
|
|
||||||
val homeServer: String? = null,
|
|
||||||
@SerialName("access_token")
|
|
||||||
val accessToken: String? = null,
|
|
||||||
@SerialName("device_id")
|
|
||||||
val deviceId: String? = null,
|
|
||||||
)
|
|
||||||
-91
@@ -1,91 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2023-2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.webkit.JavascriptInterface
|
|
||||||
import android.webkit.WebResourceRequest
|
|
||||||
import android.webkit.WebView
|
|
||||||
import android.webkit.WebViewClient
|
|
||||||
import androidx.webkit.WebViewCompat
|
|
||||||
import androidx.webkit.WebViewFeature
|
|
||||||
|
|
||||||
class WebViewMessageInterceptor(
|
|
||||||
webView: WebView,
|
|
||||||
private val debugLog: Boolean,
|
|
||||||
private val onOpenExternalUrl: (String) -> Unit,
|
|
||||||
private val onMessage: (String) -> Unit,
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
// We call both the WebMessageListener and the JavascriptInterface objects in JS with this
|
|
||||||
// 'listenerName' so they can both receive the data from the WebView when
|
|
||||||
// `${LISTENER_NAME}.postMessage(...)` is called
|
|
||||||
const val LISTENER_NAME = "elementX"
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
webView.webViewClient = object : WebViewClient() {
|
|
||||||
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
|
|
||||||
super.onPageStarted(view, url, favicon)
|
|
||||||
|
|
||||||
// We inject this JS code when the page starts loading to attach a message listener to the window.
|
|
||||||
view?.evaluateJavascript(
|
|
||||||
"""
|
|
||||||
window.addEventListener(
|
|
||||||
"mobileregistrationresponse",
|
|
||||||
(event) => {
|
|
||||||
let json = JSON.stringify(event.detail)
|
|
||||||
${"console.log('message sent: ' + json);".takeIf { debugLog }}
|
|
||||||
$LISTENER_NAME.postMessage(json);
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
""".trimIndent(),
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
|
||||||
request ?: return false
|
|
||||||
// Load the URL in a Chrome Custom Tab, and return true to cancel the load
|
|
||||||
onOpenExternalUrl(request.url.toString())
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use WebMessageListener if supported, otherwise use JavascriptInterface
|
|
||||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
|
||||||
// Create a WebMessageListener, which will receive messages from the WebView and reply to them
|
|
||||||
val webMessageListener = WebViewCompat.WebMessageListener { _, message, _, _, _ ->
|
|
||||||
onMessageReceived(message.data)
|
|
||||||
}
|
|
||||||
WebViewCompat.addWebMessageListener(
|
|
||||||
webView,
|
|
||||||
LISTENER_NAME,
|
|
||||||
setOf("*"),
|
|
||||||
webMessageListener
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
webView.addJavascriptInterface(
|
|
||||||
object {
|
|
||||||
@JavascriptInterface
|
|
||||||
fun postMessage(json: String?) {
|
|
||||||
onMessageReceived(json)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LISTENER_NAME,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onMessageReceived(json: String?) {
|
|
||||||
// Here is where we would handle the messages from the WebView, passing them to the listener
|
|
||||||
json?.let { onMessage(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-2
@@ -41,7 +41,6 @@ class OnBoardingNode(
|
|||||||
fun navigateToBugReport()
|
fun navigateToBugReport()
|
||||||
fun navigateToLoginPassword()
|
fun navigateToLoginPassword()
|
||||||
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
||||||
fun navigateToCreateAccount(url: String)
|
|
||||||
fun navigateToDeveloperSettings()
|
fun navigateToDeveloperSettings()
|
||||||
fun onDone()
|
fun onDone()
|
||||||
}
|
}
|
||||||
@@ -74,7 +73,6 @@ class OnBoardingNode(
|
|||||||
onOAuthDetails = callback::navigateToOAuth,
|
onOAuthDetails = callback::navigateToOAuth,
|
||||||
onNeedLoginPassword = callback::navigateToLoginPassword,
|
onNeedLoginPassword = callback::navigateToLoginPassword,
|
||||||
onLearnMoreClick = { openLearnMorePage(context) },
|
onLearnMoreClick = { openLearnMorePage(context) },
|
||||||
onCreateAccountContinue = callback::navigateToCreateAccount,
|
|
||||||
onBackClick = callback::onDone,
|
onBackClick = callback::onDone,
|
||||||
onDeveloperSettingsClick = callback::navigateToDeveloperSettings,
|
onDeveloperSettingsClick = callback::navigateToDeveloperSettings,
|
||||||
)
|
)
|
||||||
|
|||||||
-3
@@ -71,7 +71,6 @@ fun OnBoardingView(
|
|||||||
onOAuthDetails: (OAuthDetails) -> Unit,
|
onOAuthDetails: (OAuthDetails) -> Unit,
|
||||||
onNeedLoginPassword: () -> Unit,
|
onNeedLoginPassword: () -> Unit,
|
||||||
onLearnMoreClick: () -> Unit,
|
onLearnMoreClick: () -> Unit,
|
||||||
onCreateAccountContinue: (url: String) -> Unit,
|
|
||||||
onReportProblem: () -> Unit,
|
onReportProblem: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
@@ -84,7 +83,6 @@ fun OnBoardingView(
|
|||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onOAuthDetails = onOAuthDetails,
|
onOAuthDetails = onOAuthDetails,
|
||||||
onNeedLoginPassword = onNeedLoginPassword,
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
onCreateAccountContinue = onCreateAccountContinue,
|
|
||||||
)
|
)
|
||||||
LocalNetworkPermissionDialogView(
|
LocalNetworkPermissionDialogView(
|
||||||
dialog = state.loginModeState.localNetworkPermissionDialog,
|
dialog = state.loginModeState.localNetworkPermissionDialog,
|
||||||
@@ -366,6 +364,5 @@ internal fun OnBoardingViewPreview(
|
|||||||
onOAuthDetails = {},
|
onOAuthDetails = {},
|
||||||
onNeedLoginPassword = {},
|
onNeedLoginPassword = {},
|
||||||
onLearnMoreClick = {},
|
onLearnMoreClick = {},
|
||||||
onCreateAccountContinue = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
-55
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.web
|
|
||||||
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import dev.zacsweers.metro.AppScope
|
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
|
||||||
import io.element.android.libraries.matrix.api.TemporaryMatrixClientFactory
|
|
||||||
import io.element.android.libraries.wellknown.api.EnterpriseRemoteConfigSource
|
|
||||||
import io.element.android.libraries.wellknown.api.WellknownRetriever
|
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
interface WebClientUrlForAuthenticationRetriever {
|
|
||||||
suspend fun retrieve(homeServerUrl: String): String
|
|
||||||
}
|
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
|
||||||
class DefaultWebClientUrlForAuthenticationRetriever(
|
|
||||||
private val wellknownRetrieverFactory: WellknownRetriever.Factory,
|
|
||||||
private val temporaryMatrixClientFactory: TemporaryMatrixClientFactory,
|
|
||||||
) : WebClientUrlForAuthenticationRetriever {
|
|
||||||
override suspend fun retrieve(homeServerUrl: String): String {
|
|
||||||
if (homeServerUrl != AuthenticationConfig.MATRIX_ORG_URL) {
|
|
||||||
Timber.w("Temporary account creation flow is only supported on matrix.org")
|
|
||||||
throw AccountCreationNotSupported()
|
|
||||||
}
|
|
||||||
val temporaryMatrixClient = temporaryMatrixClientFactory.create(homeServerUrl).getOrThrow()
|
|
||||||
return temporaryMatrixClient.use {
|
|
||||||
val wellknownRetriever = wellknownRetrieverFactory.create(temporaryMatrixClient)
|
|
||||||
val wellknown = wellknownRetriever.getElementWellKnown(
|
|
||||||
host = homeServerUrl,
|
|
||||||
source = EnterpriseRemoteConfigSource.WELLKNOWN_ENDPOINT
|
|
||||||
).dataOrNull() ?: throw AccountCreationNotSupported()
|
|
||||||
|
|
||||||
val registrationHelperUrl = wellknown.registrationHelperUrl
|
|
||||||
if (registrationHelperUrl != null) {
|
|
||||||
registrationHelperUrl.toUri()
|
|
||||||
.buildUpon()
|
|
||||||
.appendQueryParameter("hs_url", homeServerUrl)
|
|
||||||
.build()
|
|
||||||
.toString()
|
|
||||||
} else {
|
|
||||||
throw AccountCreationNotSupported()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-2
@@ -84,7 +84,6 @@ class ChooseAccountProviderViewTest : RobolectricTest() {
|
|||||||
onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(),
|
onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onNeedLoginPassword: () -> Unit = EnsureNeverCalled(),
|
onNeedLoginPassword: () -> Unit = EnsureNeverCalled(),
|
||||||
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onCreateAccountContinue: (url: String) -> Unit = EnsureNeverCalledWithParam(),
|
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
ChooseAccountProviderView(
|
ChooseAccountProviderView(
|
||||||
@@ -93,7 +92,6 @@ class ChooseAccountProviderViewTest : RobolectricTest() {
|
|||||||
onOAuthDetails = onOAuthDetails,
|
onOAuthDetails = onOAuthDetails,
|
||||||
onNeedLoginPassword = onNeedLoginPassword,
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onCreateAccountContinue = onCreateAccountContinue,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-53
@@ -15,8 +15,6 @@ import io.element.android.features.login.impl.accountprovider.AccountProviderDat
|
|||||||
import io.element.android.features.login.impl.login.LoginMode
|
import io.element.android.features.login.impl.login.LoginMode
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
import io.element.android.features.login.impl.screens.onboarding.createLoginModePresenter
|
import io.element.android.features.login.impl.screens.onboarding.createLoginModePresenter
|
||||||
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
|
|
||||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
@@ -283,7 +281,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - confirm account creation without oidc and without url generates an error`() = runTest {
|
fun `present - confirm account creation without oidc generates an error`() = runTest {
|
||||||
val authenticationService = FakeMatrixAuthenticationService(
|
val authenticationService = FakeMatrixAuthenticationService(
|
||||||
setHomeserverResult = {
|
setHomeserverResult = {
|
||||||
Result.success(aMatrixHomeServerDetails())
|
Result.success(aMatrixHomeServerDetails())
|
||||||
@@ -292,9 +290,6 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val presenter = createConfirmAccountProviderPresenter(
|
val presenter = createConfirmAccountProviderPresenter(
|
||||||
params = ConfirmAccountProviderPresenter.Params(isAccountCreation = true),
|
params = ConfirmAccountProviderPresenter.Params(isAccountCreation = true),
|
||||||
matrixAuthenticationService = authenticationService,
|
matrixAuthenticationService = authenticationService,
|
||||||
webClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever {
|
|
||||||
throw AccountCreationNotSupported()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
@@ -331,64 +326,17 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - confirm account creation with OAuth and url continues with OAuth`() = runTest {
|
|
||||||
val aUrl = "aUrl"
|
|
||||||
val authenticationService = FakeMatrixAuthenticationService(
|
|
||||||
setHomeserverResult = {
|
|
||||||
Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
val presenter = createConfirmAccountProviderPresenter(
|
|
||||||
params = ConfirmAccountProviderPresenter.Params(isAccountCreation = true),
|
|
||||||
matrixAuthenticationService = authenticationService,
|
|
||||||
webClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever { aUrl },
|
|
||||||
)
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
|
||||||
skipItems(1) // Loading
|
|
||||||
val submittedState = awaitItem()
|
|
||||||
assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - confirm account creation without OAuth and with url continuing with url`() = runTest {
|
|
||||||
val aUrl = "aUrl"
|
|
||||||
val authenticationService = FakeMatrixAuthenticationService(
|
|
||||||
setHomeserverResult = {
|
|
||||||
Result.success(aMatrixHomeServerDetails())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
val presenter = createConfirmAccountProviderPresenter(
|
|
||||||
params = ConfirmAccountProviderPresenter.Params(isAccountCreation = true),
|
|
||||||
matrixAuthenticationService = authenticationService,
|
|
||||||
webClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever { aUrl },
|
|
||||||
)
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
|
||||||
skipItems(1) // Loading
|
|
||||||
val submittedState = awaitItem()
|
|
||||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.AccountCreation(aUrl))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createConfirmAccountProviderPresenter(
|
private fun createConfirmAccountProviderPresenter(
|
||||||
params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||||
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||||
defaultOAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
|
defaultOAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
|
||||||
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
|
|
||||||
) = ConfirmAccountProviderPresenter(
|
) = ConfirmAccountProviderPresenter(
|
||||||
params = params,
|
params = params,
|
||||||
accountProviderDataSource = accountProviderDataSource,
|
accountProviderDataSource = accountProviderDataSource,
|
||||||
loginModePresenter = createLoginModePresenter(
|
loginModePresenter = createLoginModePresenter(
|
||||||
authenticationService = matrixAuthenticationService,
|
authenticationService = matrixAuthenticationService,
|
||||||
oAuthActionFlow = defaultOAuthActionFlow,
|
oAuthActionFlow = defaultOAuthActionFlow,
|
||||||
webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
-124
@@ -1,124 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
|
||||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
|
||||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
|
||||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
|
||||||
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.coroutines.test.runTest
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class CreateAccountPresenterTest {
|
|
||||||
@get:Rule
|
|
||||||
val warmUpRule = WarmUpRule()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - initial state`() = runTest {
|
|
||||||
val presenter = createPresenter()
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState.url).isEqualTo("aUrl")
|
|
||||||
assertThat(initialState.pageProgress).isEqualTo(0)
|
|
||||||
assertThat(initialState.createAction).isEqualTo(AsyncAction.Uninitialized)
|
|
||||||
assertThat(initialState.isDebugBuild).isTrue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - set up progress update the state`() = runTest {
|
|
||||||
val presenter = createPresenter()
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(CreateAccountEvents.SetPageProgress(33))
|
|
||||||
assertThat(awaitItem().pageProgress).isEqualTo(33)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - receiving a message not able to be parsed change the state to error`() = runTest {
|
|
||||||
val presenter = createPresenter(
|
|
||||||
messageParser = FakeMessageParser { error("An error") }
|
|
||||||
)
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(CreateAccountEvents.OnMessageReceived(""))
|
|
||||||
assertThat(awaitItem().createAction).isInstanceOf(AsyncAction.Failure::class.java)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - receiving a message containing isTrusted is ignored`() = runTest {
|
|
||||||
val presenter = createPresenter()
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(CreateAccountEvents.OnMessageReceived("isTrusted"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - receiving a message able to be parsed change the state to success`() = runTest {
|
|
||||||
val lambda = lambdaRecorder<String, ExternalSession> { _ -> anExternalSession() }
|
|
||||||
val sessionVerificationService = FakeSessionVerificationService()
|
|
||||||
val presenter = createPresenter(
|
|
||||||
authenticationService = FakeMatrixAuthenticationService(
|
|
||||||
importCreatedSessionLambda = { Result.success(A_SESSION_ID) }
|
|
||||||
),
|
|
||||||
messageParser = FakeMessageParser(lambda),
|
|
||||||
)
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(CreateAccountEvents.OnMessageReceived("aMessage"))
|
|
||||||
assertThat(awaitItem().createAction.isLoading()).isTrue()
|
|
||||||
sessionVerificationService.emitVerifiedStatus(SessionVerifiedStatus.Verified)
|
|
||||||
assertThat(awaitItem().createAction.dataOrNull()).isEqualTo(A_SESSION_ID)
|
|
||||||
}
|
|
||||||
lambda.assertions().isCalledOnce().with(value("aMessage"))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - receiving a message able to be parsed but error in importing change the state to error`() = runTest {
|
|
||||||
val presenter = createPresenter(
|
|
||||||
authenticationService = FakeMatrixAuthenticationService(
|
|
||||||
importCreatedSessionLambda = { Result.failure(AN_EXCEPTION) }
|
|
||||||
),
|
|
||||||
messageParser = FakeMessageParser { anExternalSession() }
|
|
||||||
)
|
|
||||||
presenter.test {
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(CreateAccountEvents.OnMessageReceived(""))
|
|
||||||
assertThat(awaitItem().createAction.isLoading()).isTrue()
|
|
||||||
assertThat(awaitItem().createAction.errorOrNull()).isNotNull()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createPresenter(
|
|
||||||
url: String = "aUrl",
|
|
||||||
authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
|
||||||
messageParser: MessageParser = FakeMessageParser(),
|
|
||||||
buildMeta: BuildMeta = aBuildMeta(),
|
|
||||||
) = CreateAccountPresenter(
|
|
||||||
url = url,
|
|
||||||
authenticationService = authenticationService,
|
|
||||||
messageParser = messageParser,
|
|
||||||
buildMeta = buildMeta,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
-87
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
|
||||||
import io.element.android.libraries.androidutils.json.DefaultJsonProvider
|
|
||||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
|
||||||
import kotlinx.serialization.SerializationException
|
|
||||||
import org.junit.Assert.assertThrows
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class DefaultMessageParserTest {
|
|
||||||
private val validMessage = """
|
|
||||||
{
|
|
||||||
"user_id": "user_id",
|
|
||||||
"home_server": "home_server",
|
|
||||||
"access_token": "access_token",
|
|
||||||
"device_id": "device_id"
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `DefaultMessageParser is able to parse correct message`() {
|
|
||||||
val sut = createDefaultMessageParser()
|
|
||||||
assertThat(sut.parse(validMessage)).isEqualTo(
|
|
||||||
anExternalSession(
|
|
||||||
homeserverUrl = "home_server",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `DefaultMessageParser should throw Exception in case of error`() {
|
|
||||||
val sut = createDefaultMessageParser()
|
|
||||||
// kotlinx.serialization.json.internal.JsonDecodingException
|
|
||||||
assertThrows(SerializationException::class.java) { sut.parse("invalid json") }
|
|
||||||
// missing userId
|
|
||||||
assertThrows(IllegalStateException::class.java) { sut.parse(validMessage.replace(""""user_id": "user_id",""", "")) }
|
|
||||||
// missing accessToken
|
|
||||||
assertThrows(IllegalStateException::class.java) { sut.parse(validMessage.replace(""""access_token": "access_token",""", "")) }
|
|
||||||
// missing deviceId
|
|
||||||
assertThrows(IllegalStateException::class.java) {
|
|
||||||
sut.parse(
|
|
||||||
validMessage
|
|
||||||
.replace(""""access_token": "access_token",""", """"access_token": "access_token"""")
|
|
||||||
.replace(""""device_id": "device_id"""", "")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `DefaultMessageParser should be successful even is homeserver url is missing`() {
|
|
||||||
val sut = createDefaultMessageParser()
|
|
||||||
// missing homeServer
|
|
||||||
assertThat(sut.parse(validMessage.replace(""""home_server": "home_server",""", ""))).isEqualTo(
|
|
||||||
anExternalSession(
|
|
||||||
homeserverUrl = AuthenticationConfig.MATRIX_ORG_URL,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createDefaultMessageParser(): DefaultMessageParser {
|
|
||||||
return DefaultMessageParser(
|
|
||||||
accountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
|
||||||
json = DefaultJsonProvider(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun anExternalSession(
|
|
||||||
homeserverUrl: String = "home_server",
|
|
||||||
) = ExternalSession(
|
|
||||||
userId = "user_id",
|
|
||||||
homeserverUrl = homeserverUrl,
|
|
||||||
accessToken = "access_token",
|
|
||||||
deviceId = "device_id",
|
|
||||||
refreshToken = null,
|
|
||||||
)
|
|
||||||
-20
@@ -1,20 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.screens.createaccount
|
|
||||||
|
|
||||||
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
|
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
|
||||||
|
|
||||||
class FakeMessageParser(
|
|
||||||
private val parseResult: (String) -> ExternalSession = { lambdaError() }
|
|
||||||
) : MessageParser {
|
|
||||||
override fun parse(message: String): ExternalSession {
|
|
||||||
return parseResult(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-4
@@ -17,8 +17,6 @@ import io.element.android.features.login.impl.accesscontrol.DefaultAccountProvid
|
|||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||||
import io.element.android.features.login.impl.login.LoginModePresenter
|
import io.element.android.features.login.impl.login.LoginModePresenter
|
||||||
import io.element.android.features.login.impl.web.FakeWebClientUrlForAuthenticationRetriever
|
|
||||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
|
||||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||||
import io.element.android.features.wellknown.test.FakeWellknownRetrieverFactory
|
import io.element.android.features.wellknown.test.FakeWellknownRetrieverFactory
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
@@ -322,7 +320,6 @@ private fun createPresenter(
|
|||||||
fun createLoginModePresenter(
|
fun createLoginModePresenter(
|
||||||
oAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
|
oAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
|
||||||
authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||||
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
|
|
||||||
localNetworkPermissionAdvisor: LocalNetworkPermissionAdvisor =
|
localNetworkPermissionAdvisor: LocalNetworkPermissionAdvisor =
|
||||||
FakeLocalNetworkPermissionAdvisor(),
|
FakeLocalNetworkPermissionAdvisor(),
|
||||||
permissionsPresenterFactory: PermissionsPresenter.Factory =
|
permissionsPresenterFactory: PermissionsPresenter.Factory =
|
||||||
@@ -330,7 +327,6 @@ fun createLoginModePresenter(
|
|||||||
): LoginModePresenter = LoginModePresenter(
|
): LoginModePresenter = LoginModePresenter(
|
||||||
oAuthActionFlow = oAuthActionFlow,
|
oAuthActionFlow = oAuthActionFlow,
|
||||||
authenticationService = authenticationService,
|
authenticationService = authenticationService,
|
||||||
webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
|
|
||||||
localNetworkPermissionGate = LocalNetworkPermissionGate(
|
localNetworkPermissionGate = LocalNetworkPermissionGate(
|
||||||
advisor = localNetworkPermissionAdvisor,
|
advisor = localNetworkPermissionAdvisor,
|
||||||
permissionsPresenterFactory = permissionsPresenterFactory,
|
permissionsPresenterFactory = permissionsPresenterFactory,
|
||||||
|
|||||||
-18
@@ -234,22 +234,6 @@ class OnboardingViewTest : RobolectricTestParameter() {
|
|||||||
eventSink.assertSingle(OnBoardingEvents.ClearError)
|
eventSink.assertSingle(OnBoardingEvents.ClearError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `when success AccountCreation - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest {
|
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>()
|
|
||||||
val oAuthDetails = OAuthDetails("aUrl")
|
|
||||||
ensureCalledOnceWithParam(oAuthDetails.url) { callback ->
|
|
||||||
setOnboardingView(
|
|
||||||
state = anOnBoardingState(
|
|
||||||
loginModeState = aLoginModeState(loginMode = AsyncData.Success(LoginMode.AccountCreation("aUrl"))),
|
|
||||||
eventSink = eventSink,
|
|
||||||
),
|
|
||||||
onCreateAccountContinue = callback,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
eventSink.assertSingle(OnBoardingEvents.ClearError)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AndroidComposeUiTest<ComponentActivity>.setOnboardingView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setOnboardingView(
|
||||||
state: OnBoardingState,
|
state: OnBoardingState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
@@ -261,7 +245,6 @@ class OnboardingViewTest : RobolectricTestParameter() {
|
|||||||
onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(),
|
onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onNeedLoginPassword: () -> Unit = EnsureNeverCalled(),
|
onNeedLoginPassword: () -> Unit = EnsureNeverCalled(),
|
||||||
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onCreateAccountContinue: (url: String) -> Unit = EnsureNeverCalledWithParam(),
|
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
OnBoardingView(
|
OnBoardingView(
|
||||||
@@ -275,7 +258,6 @@ class OnboardingViewTest : RobolectricTestParameter() {
|
|||||||
onOAuthDetails = onOAuthDetails,
|
onOAuthDetails = onOAuthDetails,
|
||||||
onNeedLoginPassword = onNeedLoginPassword,
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onCreateAccountContinue = onCreateAccountContinue,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2024, 2025 New Vector 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.login.impl.web
|
|
||||||
|
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
|
||||||
|
|
||||||
class FakeWebClientUrlForAuthenticationRetriever(
|
|
||||||
private val retrieveLambda: suspend (homeServerUrl: String) -> String = { lambdaError() }
|
|
||||||
) : WebClientUrlForAuthenticationRetriever {
|
|
||||||
override suspend fun retrieve(homeServerUrl: String): String {
|
|
||||||
return retrieveLambda(homeServerUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user