diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt index 978d28dfa3..a662f20351 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/LoginFlowNode.kt @@ -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.classic.ClassicFlowNode 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.onboarding.OnBoardingNode import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode @@ -140,9 +139,6 @@ class LoginFlowNode( data class LoginPassword( val initialLogin: String = "", ) : NavTarget - - @Parcelize - data class CreateAccount(val url: String) : NavTarget } override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { @@ -164,10 +160,6 @@ class LoginFlowNode( override fun navigateToOAuth(oAuthDetails: OAuthDetails) { navigateToMas(oAuthDetails) } - - override fun navigateToCreateAccount(url: String) { - backstack.push(NavTarget.CreateAccount(url)) - } } createNode(buildContext, listOf(callback)) } @@ -201,10 +193,6 @@ class LoginFlowNode( navigateToMas(oAuthDetails) } - override fun navigateToCreateAccount(url: String) { - backstack.push(NavTarget.CreateAccount(url)) - } - override fun navigateToDeveloperSettings() { backstack.push(NavTarget.AppDeveloperSettings) } @@ -247,10 +235,6 @@ class LoginFlowNode( navigateToMas(oAuthDetails) } - override fun navigateToCreateAccount(url: String) { - backstack.push(NavTarget.CreateAccount(url)) - } - override fun navigateToLoginPassword() { backstack.push(NavTarget.LoginPassword()) } @@ -274,10 +258,6 @@ class LoginFlowNode( navigateToMas(oAuthDetails) } - override fun navigateToCreateAccount(url: String) { - backstack.push(NavTarget.CreateAccount(url)) - } - override fun navigateToLoginPassword() { backstack.push(NavTarget.LoginPassword()) } @@ -324,12 +304,6 @@ class LoginFlowNode( ) createNode(buildContext, plugins = listOf(inputs)) } - is NavTarget.CreateAccount -> { - val inputs = CreateAccountNode.Inputs( - url = navTarget.url, - ) - createNode(buildContext, listOf(inputs)) - } } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModePresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModePresenter.kt index 34188095b0..466cd210ab 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModePresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModePresenter.kt @@ -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.createaccount.AccountCreationNotSupported 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.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState @@ -39,7 +38,6 @@ import io.element.android.libraries.oauth.api.OAuthActionFlow class LoginModePresenter( private val oAuthActionFlow: OAuthActionFlow, private val authenticationService: MatrixAuthenticationService, - private val webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever, private val localNetworkPermissionGate: LocalNetworkPermissionGate, ) : Presenter { @Composable @@ -90,11 +88,9 @@ class LoginModePresenter( authenticationService.getOAuthUrl(prompt = oAuthPrompt, loginHint = request.loginHint).getOrThrow() ) } - request.isAccountCreation -> LoginMode.AccountCreation( - webClientUrlForAuthenticationRetriever.retrieve(request.homeserverUrl) - ) + request.isAccountCreation -> throw AccountCreationNotSupported() matrixHomeServerDetails.supportsPasswordLogin -> LoginMode.PasswordLogin - else -> error("Unsupported login flow") + else -> error("Unsupported authentication flow") } }.getOrThrow() }.runCatchingUpdatingState( diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeState.kt index 849ff31f85..86dd550ea6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeState.kt @@ -20,5 +20,4 @@ data class LoginModeState( sealed interface LoginMode { data object PasswordLogin : LoginMode data class OAuth(val oAuthDetails: OAuthDetails) : LoginMode - data class AccountCreation(val url: String) : LoginMode } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt index 3549e17457..8b59db1d61 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/login/LoginModeView.kt @@ -34,7 +34,6 @@ fun LoginModeView( onLearnMoreClick: () -> Unit, onOAuthDetails: (OAuthDetails) -> Unit, onNeedLoginPassword: () -> Unit, - onCreateAccountContinue: (url: String) -> Unit ) { val context = LocalContext.current when (loginMode) { @@ -120,7 +119,6 @@ fun LoginModeView( when (val loginModeData = loginMode.data) { is LoginMode.OAuth -> onOAuthDetails(loginModeData.oAuthDetails) LoginMode.PasswordLogin -> onNeedLoginPassword() - is LoginMode.AccountCreation -> onCreateAccountContinue(loginModeData.url) } // Also clear the data, to let the next screen be able to go back onClearError() @@ -139,7 +137,6 @@ internal fun LoginModeViewPreview(@PreviewParameter(LoginModeViewErrorProvider:: onLearnMoreClick = {}, onOAuthDetails = {}, onNeedLoginPassword = {}, - onCreateAccountContinue = {} ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt index 5f79f197d9..6432042818 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderNode.kt @@ -32,7 +32,6 @@ class ChooseAccountProviderNode( interface Callback : Plugin { fun navigateToLoginPassword() fun navigateToOAuth(oAuthDetails: OAuthDetails) - fun navigateToCreateAccount(url: String) } private val callback: Callback = callback() @@ -48,7 +47,6 @@ class ChooseAccountProviderNode( onOAuthDetails = callback::navigateToOAuth, onNeedLoginPassword = callback::navigateToLoginPassword, onLearnMoreClick = { openLearnMorePage(context) }, - onCreateAccountContinue = callback::navigateToCreateAccount, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt index e858789bb4..0d9d8cfba7 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderView.kt @@ -57,7 +57,6 @@ fun ChooseAccountProviderView( onOAuthDetails: (OAuthDetails) -> Unit, onNeedLoginPassword: () -> Unit, onLearnMoreClick: () -> Unit, - onCreateAccountContinue: (url: String) -> Unit, modifier: Modifier = Modifier, ) { val isLoading by remember(state.loginModeState.loginMode) { @@ -137,7 +136,6 @@ fun ChooseAccountProviderView( onLearnMoreClick = onLearnMoreClick, onOAuthDetails = onOAuthDetails, onNeedLoginPassword = onNeedLoginPassword, - onCreateAccountContinue = onCreateAccountContinue, ) } } @@ -161,6 +159,5 @@ internal fun ChooseAccountProviderViewPreview(@PreviewParameter(ChooseAccountPro onLearnMoreClick = { }, onOAuthDetails = { }, onNeedLoginPassword = { }, - onCreateAccountContinue = { }, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt index cfbd86f363..160b500cf6 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt @@ -55,7 +55,6 @@ class ClassicFlowNode( fun navigateToOnBoarding(allowBackNavigation: Boolean) fun navigateToLoginPassword() fun navigateToOAuth(oAuthDetails: OAuthDetails) - fun navigateToCreateAccount(url: String) } sealed interface NavTarget : Parcelable { @@ -115,10 +114,6 @@ class ClassicFlowNode( callback.navigateToOAuth(oAuthDetails) } - override fun navigateToCreateAccount(url: String) { - callback.navigateToCreateAccount(url) - } - override fun navigateToMissingKeyBackup() { backstack.push(NavTarget.MissingKeyBackup) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicNode.kt index d5acca38ae..ff56b0e8e8 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicNode.kt @@ -36,7 +36,6 @@ class LoginWithClassicNode( fun navigateToOtherOptions() fun navigateToLoginPassword() fun navigateToOAuth(oAuthDetails: OAuthDetails) - fun navigateToCreateAccount(url: String) fun navigateToMissingKeyBackup() } @@ -63,7 +62,6 @@ class LoginWithClassicNode( onOAuthDetails = callback::navigateToOAuth, onNeedLoginPassword = callback::navigateToLoginPassword, onLearnMoreClick = { openLearnMorePage(context) }, - onCreateAccountContinue = callback::navigateToCreateAccount, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt index 75181a068e..81638b4be0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt @@ -64,7 +64,6 @@ fun LoginWithClassicView( onOAuthDetails: (OAuthDetails) -> Unit, onNeedLoginPassword: () -> Unit, onLearnMoreClick: () -> Unit, - onCreateAccountContinue: (url: String) -> Unit, modifier: Modifier = Modifier, ) { val isLoading by remember(state.loginModeState.loginMode) { @@ -204,7 +203,6 @@ fun LoginWithClassicView( onLearnMoreClick = onLearnMoreClick, onOAuthDetails = onOAuthDetails, onNeedLoginPassword = onNeedLoginPassword, - onCreateAccountContinue = onCreateAccountContinue, ) LocalNetworkPermissionDialogView( dialog = state.loginModeState.localNetworkPermissionDialog, @@ -226,6 +224,5 @@ internal fun LoginWithClassicViewPreview(@PreviewParameter(LoginWithClassicState onOAuthDetails = {}, onNeedLoginPassword = {}, onLearnMoreClick = {}, - onCreateAccountContinue = {}, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt index 928a493dc1..0accaea767 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderNode.kt @@ -45,7 +45,6 @@ class ConfirmAccountProviderNode( interface Callback : Plugin { fun navigateToLoginPassword() fun navigateToOAuth(oAuthDetails: OAuthDetails) - fun navigateToCreateAccount(url: String) fun navigateToChangeAccountProvider() } @@ -60,7 +59,6 @@ class ConfirmAccountProviderNode( modifier = modifier, onOAuthDetails = callback::navigateToOAuth, onNeedLoginPassword = callback::navigateToLoginPassword, - onCreateAccountContinue = callback::navigateToCreateAccount, onChange = callback::navigateToChangeAccountProvider, onLearnMoreClick = { openLearnMorePage(context) }, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt index 95fbff25ab..3702b195a1 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderView.kt @@ -43,7 +43,6 @@ fun ConfirmAccountProviderView( onOAuthDetails: (OAuthDetails) -> Unit, onNeedLoginPassword: () -> Unit, onLearnMoreClick: () -> Unit, - onCreateAccountContinue: (url: String) -> Unit, onChange: () -> Unit, modifier: Modifier = Modifier, ) { @@ -107,7 +106,6 @@ fun ConfirmAccountProviderView( onLearnMoreClick = onLearnMoreClick, onOAuthDetails = onOAuthDetails, onNeedLoginPassword = onNeedLoginPassword, - onCreateAccountContinue = onCreateAccountContinue, ) } @@ -131,7 +129,6 @@ internal fun ConfirmAccountProviderViewPreview( state = state, onOAuthDetails = {}, onNeedLoginPassword = {}, - onCreateAccountContinue = {}, onLearnMoreClick = {}, onChange = {}, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountEvents.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountEvents.kt deleted file mode 100644 index a51557347e..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountEvents.kt +++ /dev/null @@ -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 -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountNode.kt deleted file mode 100644 index e1972e5a0f..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountNode.kt +++ /dev/null @@ -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, - presenterFactory: CreateAccountPresenter.Factory, -) : Node(buildContext, plugins = plugins) { - data class Inputs( - val url: String, - ) : NodeInputs - - private val presenter = presenterFactory.create(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) - }, - ) - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenter.kt deleted file mode 100644 index bf7af111f4..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenter.kt +++ /dev/null @@ -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 { - @AssistedFactory - interface Factory { - fun create(url: String): CreateAccountPresenter - } - - @Composable - override fun present(): CreateAccountState { - val coroutineScope = rememberCoroutineScope() - val pageProgress: MutableState = remember { mutableIntStateOf(0) } - val createAction: MutableState> = 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>) = 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) - } - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountState.kt deleted file mode 100644 index de7efe573e..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountState.kt +++ /dev/null @@ -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, - val isDebugBuild: Boolean, - val eventSink: (CreateAccountEvents) -> Unit -) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt deleted file mode 100644 index 976ceae70d..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountStateProvider.kt +++ /dev/null @@ -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 { - override val values: Sequence - 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 = AsyncAction.Uninitialized, -) = CreateAccountState( - url = "https://example.com", - isDebugBuild = true, - pageProgress = pageProgress, - createAction = createAction, - eventSink = {} -) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountView.kt deleted file mode 100644 index 051188627a..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountView.kt +++ /dev/null @@ -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 = {}, - ) -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt deleted file mode 100644 index d7954373bc..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MessageParser.kt +++ /dev/null @@ -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, - ) - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MobileRegistrationResponse.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MobileRegistrationResponse.kt deleted file mode 100644 index 5e78b1bd37..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/MobileRegistrationResponse.kt +++ /dev/null @@ -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, -) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/WebViewMessageInterceptor.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/WebViewMessageInterceptor.kt deleted file mode 100644 index 20d503522b..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/createaccount/WebViewMessageInterceptor.kt +++ /dev/null @@ -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) } - } -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt index 99f7e86fd3..244e684c5b 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingNode.kt @@ -41,7 +41,6 @@ class OnBoardingNode( fun navigateToBugReport() fun navigateToLoginPassword() fun navigateToOAuth(oAuthDetails: OAuthDetails) - fun navigateToCreateAccount(url: String) fun navigateToDeveloperSettings() fun onDone() } @@ -74,7 +73,6 @@ class OnBoardingNode( onOAuthDetails = callback::navigateToOAuth, onNeedLoginPassword = callback::navigateToLoginPassword, onLearnMoreClick = { openLearnMorePage(context) }, - onCreateAccountContinue = callback::navigateToCreateAccount, onBackClick = callback::onDone, onDeveloperSettingsClick = callback::navigateToDeveloperSettings, ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt index bf96b2dbe0..10fe3a854c 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingView.kt @@ -71,7 +71,6 @@ fun OnBoardingView( onOAuthDetails: (OAuthDetails) -> Unit, onNeedLoginPassword: () -> Unit, onLearnMoreClick: () -> Unit, - onCreateAccountContinue: (url: String) -> Unit, onReportProblem: () -> Unit, modifier: Modifier = Modifier, ) { @@ -84,7 +83,6 @@ fun OnBoardingView( onLearnMoreClick = onLearnMoreClick, onOAuthDetails = onOAuthDetails, onNeedLoginPassword = onNeedLoginPassword, - onCreateAccountContinue = onCreateAccountContinue, ) LocalNetworkPermissionDialogView( dialog = state.loginModeState.localNetworkPermissionDialog, @@ -366,6 +364,5 @@ internal fun OnBoardingViewPreview( onOAuthDetails = {}, onNeedLoginPassword = {}, onLearnMoreClick = {}, - onCreateAccountContinue = {}, ) } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt deleted file mode 100644 index 2902126417..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/web/WebClientUrlForAuthenticationRetriever.kt +++ /dev/null @@ -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() - } - } - } -} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt index f1ce703d1c..cd64638563 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/chooseaccountprovider/ChooseAccountProviderViewTest.kt @@ -84,7 +84,6 @@ class ChooseAccountProviderViewTest : RobolectricTest() { onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(), onNeedLoginPassword: () -> Unit = EnsureNeverCalled(), onLearnMoreClick: () -> Unit = EnsureNeverCalled(), - onCreateAccountContinue: (url: String) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { ChooseAccountProviderView( @@ -93,7 +92,6 @@ class ChooseAccountProviderViewTest : RobolectricTest() { onOAuthDetails = onOAuthDetails, onNeedLoginPassword = onNeedLoginPassword, onLearnMoreClick = onLearnMoreClick, - onCreateAccountContinue = onCreateAccountContinue, ) } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt index c20896870b..52858b77af 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/confirmaccountprovider/ConfirmAccountProviderPresenterTest.kt @@ -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.screens.createaccount.AccountCreationNotSupported 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.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.test.AN_EXCEPTION @@ -283,7 +281,7 @@ class ConfirmAccountProviderPresenterTest { } @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( setHomeserverResult = { Result.success(aMatrixHomeServerDetails()) @@ -292,9 +290,6 @@ class ConfirmAccountProviderPresenterTest { val presenter = createConfirmAccountProviderPresenter( params = ConfirmAccountProviderPresenter.Params(isAccountCreation = true), matrixAuthenticationService = authenticationService, - webClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever { - throw AccountCreationNotSupported() - }, ) presenter.test { 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( params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false), accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()), matrixAuthenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(), defaultOAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(), - webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(), ) = ConfirmAccountProviderPresenter( params = params, accountProviderDataSource = accountProviderDataSource, loginModePresenter = createLoginModePresenter( authenticationService = matrixAuthenticationService, oAuthActionFlow = defaultOAuthActionFlow, - webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever, ), ) } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenterTest.kt deleted file mode 100644 index c70a5c9a9a..0000000000 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/CreateAccountPresenterTest.kt +++ /dev/null @@ -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 { _ -> 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, - ) -} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/DefaultMessageParserTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/DefaultMessageParserTest.kt deleted file mode 100644 index 680fda2838..0000000000 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/DefaultMessageParserTest.kt +++ /dev/null @@ -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, -) diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/FakeMessageParser.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/FakeMessageParser.kt deleted file mode 100644 index 62d6dd7dd4..0000000000 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/createaccount/FakeMessageParser.kt +++ /dev/null @@ -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) - } -} diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt index 782ff00ec0..fbee19f05e 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnBoardingPresenterTest.kt @@ -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.localnetwork.LocalNetworkPermissionGate 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.FakeWellknownRetrieverFactory import io.element.android.libraries.architecture.AsyncData @@ -322,7 +320,6 @@ private fun createPresenter( fun createLoginModePresenter( oAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(), authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(), - webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(), localNetworkPermissionAdvisor: LocalNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(), permissionsPresenterFactory: PermissionsPresenter.Factory = @@ -330,7 +327,6 @@ fun createLoginModePresenter( ): LoginModePresenter = LoginModePresenter( oAuthActionFlow = oAuthActionFlow, authenticationService = authenticationService, - webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever, localNetworkPermissionGate = LocalNetworkPermissionGate( advisor = localNetworkPermissionAdvisor, permissionsPresenterFactory = permissionsPresenterFactory, diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt index 1bdcb9bda1..dfa4119711 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/onboarding/OnboardingViewTest.kt @@ -234,22 +234,6 @@ class OnboardingViewTest : RobolectricTestParameter() { eventSink.assertSingle(OnBoardingEvents.ClearError) } - @Test - fun `when success AccountCreation - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest { - val eventSink = EventsRecorder() - 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.setOnboardingView( state: OnBoardingState, onBackClick: () -> Unit = EnsureNeverCalled(), @@ -261,7 +245,6 @@ class OnboardingViewTest : RobolectricTestParameter() { onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(), onNeedLoginPassword: () -> Unit = EnsureNeverCalled(), onLearnMoreClick: () -> Unit = EnsureNeverCalled(), - onCreateAccountContinue: (url: String) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { OnBoardingView( @@ -275,7 +258,6 @@ class OnboardingViewTest : RobolectricTestParameter() { onOAuthDetails = onOAuthDetails, onNeedLoginPassword = onNeedLoginPassword, onLearnMoreClick = onLearnMoreClick, - onCreateAccountContinue = onCreateAccountContinue, ) } } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/web/FakeWebClientUrlForAuthenticationRetriever.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/web/FakeWebClientUrlForAuthenticationRetriever.kt deleted file mode 100644 index f52af0adf3..0000000000 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/web/FakeWebClientUrlForAuthenticationRetriever.kt +++ /dev/null @@ -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) - } -}