mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Consolidate account provider selection into a single editable screen
This commit is contained in:
-43
@@ -23,7 +23,6 @@ import com.bumble.appyx.navmodel.backstack.BackStack
|
|||||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.replace
|
import com.bumble.appyx.navmodel.backstack.operation.replace
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.singleTop
|
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
@@ -33,14 +32,12 @@ import io.element.android.features.login.api.LoginEntryPoint
|
|||||||
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.classic.ElementClassicConnection
|
import io.element.android.features.login.impl.classic.ElementClassicConnection
|
||||||
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
|
import io.element.android.features.login.impl.qrcode.QrCodeLoginFlowNode
|
||||||
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
|
|
||||||
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.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.preferences.api.PreferencesEntryPoint
|
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||||
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
@@ -130,12 +127,6 @@ class LoginFlowNode(
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
data object ChooseAccountProvider : NavTarget
|
data object ChooseAccountProvider : NavTarget
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data object ChangeAccountProvider : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data object SearchAccountProvider : NavTarget
|
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class LoginPassword(
|
data class LoginPassword(
|
||||||
val initialLogin: String = "",
|
val initialLogin: String = "",
|
||||||
@@ -281,43 +272,9 @@ class LoginFlowNode(
|
|||||||
override fun navigateToLoginPassword() {
|
override fun navigateToLoginPassword() {
|
||||||
backstack.push(NavTarget.LoginPassword())
|
backstack.push(NavTarget.LoginPassword())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToChangeAccountProvider() {
|
|
||||||
backstack.push(NavTarget.ChangeAccountProvider)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
createNode<ConfirmAccountProviderNode>(buildContext, plugins = listOf(inputs, callback))
|
createNode<ConfirmAccountProviderNode>(buildContext, plugins = listOf(inputs, callback))
|
||||||
}
|
}
|
||||||
NavTarget.ChangeAccountProvider -> {
|
|
||||||
val callback = object : ChangeAccountProviderNode.Callback {
|
|
||||||
override fun onDone() {
|
|
||||||
// Go back to the Account Provider screen
|
|
||||||
val confirmAccountProvider = backstack.elements.value.firstOrNull {
|
|
||||||
it.key.navTarget is NavTarget.ConfirmAccountProvider
|
|
||||||
}?.key?.navTarget ?: NavTarget.ConfirmAccountProvider(isAccountCreation = false)
|
|
||||||
backstack.singleTop(confirmAccountProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun navigateToSearchAccountProvider() {
|
|
||||||
backstack.push(NavTarget.SearchAccountProvider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createNode<ChangeAccountProviderNode>(buildContext, plugins = listOf(callback))
|
|
||||||
}
|
|
||||||
NavTarget.SearchAccountProvider -> {
|
|
||||||
val callback = object : SearchAccountProviderNode.Callback {
|
|
||||||
override fun onDone() {
|
|
||||||
// Go back to the Account Provider screen
|
|
||||||
val confirmAccountProvider = backstack.elements.value.firstOrNull {
|
|
||||||
it.key.navTarget is NavTarget.ConfirmAccountProvider
|
|
||||||
}?.key?.navTarget ?: NavTarget.ConfirmAccountProvider(isAccountCreation = false)
|
|
||||||
backstack.singleTop(confirmAccountProvider)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createNode<SearchAccountProviderNode>(buildContext, plugins = listOf(callback))
|
|
||||||
}
|
|
||||||
is NavTarget.LoginPassword -> {
|
is NavTarget.LoginPassword -> {
|
||||||
val inputs = LoginPasswordNode.Inputs(
|
val inputs = LoginPasswordNode.Inputs(
|
||||||
initialLogin = navTarget.initialLogin,
|
initialLogin = navTarget.initialLogin,
|
||||||
|
|||||||
+1
@@ -9,6 +9,7 @@
|
|||||||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
|
|
||||||
sealed interface ConfirmAccountProviderEvents {
|
sealed interface ConfirmAccountProviderEvents {
|
||||||
|
data class UserInputChanged(val accountProvider: String) : ConfirmAccountProviderEvents
|
||||||
data object Continue : ConfirmAccountProviderEvents
|
data object Continue : ConfirmAccountProviderEvents
|
||||||
data object ClearError : ConfirmAccountProviderEvents
|
data object ClearError : ConfirmAccountProviderEvents
|
||||||
}
|
}
|
||||||
|
|||||||
-2
@@ -46,7 +46,6 @@ class ConfirmAccountProviderNode(
|
|||||||
fun navigateToLoginPassword()
|
fun navigateToLoginPassword()
|
||||||
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
fun navigateToOAuth(oAuthDetails: OAuthDetails)
|
||||||
fun navigateToCreateAccount(url: String)
|
fun navigateToCreateAccount(url: String)
|
||||||
fun navigateToChangeAccountProvider()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback: Callback = callback()
|
private val callback: Callback = callback()
|
||||||
@@ -61,7 +60,6 @@ class ConfirmAccountProviderNode(
|
|||||||
onOAuthDetails = callback::navigateToOAuth,
|
onOAuthDetails = callback::navigateToOAuth,
|
||||||
onNeedLoginPassword = callback::navigateToLoginPassword,
|
onNeedLoginPassword = callback::navigateToLoginPassword,
|
||||||
onCreateAccountContinue = callback::navigateToCreateAccount,
|
onCreateAccountContinue = callback::navigateToCreateAccount,
|
||||||
onChange = callback::navigateToChangeAccountProvider,
|
|
||||||
onLearnMoreClick = { openLearnMorePage(context) },
|
onLearnMoreClick = { openLearnMorePage(context) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-6
@@ -9,14 +9,23 @@
|
|||||||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedFactory
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
|
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||||
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.changeserver.ChangeServerEvents
|
||||||
|
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||||
import io.element.android.features.login.impl.login.LoginModeEvent
|
import io.element.android.features.login.impl.login.LoginModeEvent
|
||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
@@ -24,6 +33,7 @@ class ConfirmAccountProviderPresenter(
|
|||||||
@Assisted private val params: Params,
|
@Assisted private val params: Params,
|
||||||
private val accountProviderDataSource: AccountProviderDataSource,
|
private val accountProviderDataSource: AccountProviderDataSource,
|
||||||
private val loginModePresenter: Presenter<LoginModeState>,
|
private val loginModePresenter: Presenter<LoginModeState>,
|
||||||
|
private val changeServerPresenter: Presenter<ChangeServerState>,
|
||||||
) : Presenter<ConfirmAccountProviderState> {
|
) : Presenter<ConfirmAccountProviderState> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val isAccountCreation: Boolean,
|
val isAccountCreation: Boolean,
|
||||||
@@ -38,25 +48,49 @@ class ConfirmAccountProviderPresenter(
|
|||||||
override fun present(): ConfirmAccountProviderState {
|
override fun present(): ConfirmAccountProviderState {
|
||||||
val accountProvider by accountProviderDataSource.flow.collectAsState()
|
val accountProvider by accountProviderDataSource.flow.collectAsState()
|
||||||
val loginModeState = loginModePresenter.present()
|
val loginModeState = loginModePresenter.present()
|
||||||
|
val changeServerState = changeServerPresenter.present()
|
||||||
|
|
||||||
fun handleEvent(event: ConfirmAccountProviderEvents) {
|
// Editable input, seeded from the current (history-defaulted) account provider until the user edits it.
|
||||||
when (event) {
|
var userInput by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
ConfirmAccountProviderEvents.Continue -> loginModeState.eventSink(
|
val accountProviderInput = userInput ?: accountProvider.url
|
||||||
|
val latestAccountProviderInput by rememberUpdatedState(accountProviderInput)
|
||||||
|
|
||||||
|
// Once the chosen account provider has been validated and persisted, proceed with the actual sign in / sign up.
|
||||||
|
LaunchedEffect(changeServerState.changeServerAction) {
|
||||||
|
if (changeServerState.changeServerAction is AsyncData.Success) {
|
||||||
|
loginModeState.eventSink(
|
||||||
LoginModeEvent.Submit(
|
LoginModeEvent.Submit(
|
||||||
isAccountCreation = params.isAccountCreation,
|
isAccountCreation = params.isAccountCreation,
|
||||||
homeserverUrl = accountProvider.url,
|
homeserverUrl = latestAccountProviderInput.trim(),
|
||||||
resolvedHomeserverUrl = null,
|
resolvedHomeserverUrl = null,
|
||||||
loginHint = null,
|
loginHint = null,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
ConfirmAccountProviderEvents.ClearError -> loginModeState.eventSink(LoginModeEvent.ClearError)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleEvent(event: ConfirmAccountProviderEvents) {
|
||||||
|
when (event) {
|
||||||
|
is ConfirmAccountProviderEvents.UserInputChanged -> {
|
||||||
|
userInput = event.accountProvider
|
||||||
|
}
|
||||||
|
// Validate (and persist) the chosen account provider before proceeding. This also enforces the
|
||||||
|
// enterprise account-provider access control, which the login submit does not do on its own.
|
||||||
|
ConfirmAccountProviderEvents.Continue -> changeServerState.eventSink(
|
||||||
|
ChangeServerEvents.ChangeServer(AccountProvider(url = accountProviderInput.trim()))
|
||||||
|
)
|
||||||
|
ConfirmAccountProviderEvents.ClearError -> {
|
||||||
|
loginModeState.eventSink(LoginModeEvent.ClearError)
|
||||||
|
changeServerState.eventSink(ChangeServerEvents.ClearError)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ConfirmAccountProviderState(
|
return ConfirmAccountProviderState(
|
||||||
accountProvider = accountProvider,
|
accountProviderInput = accountProviderInput,
|
||||||
isAccountCreation = params.isAccountCreation,
|
isAccountCreation = params.isAccountCreation,
|
||||||
loginModeState = loginModeState,
|
loginModeState = loginModeState,
|
||||||
|
changeServerState = changeServerState,
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-4
@@ -8,17 +8,23 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
|
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
||||||
data class ConfirmAccountProviderState(
|
data class ConfirmAccountProviderState(
|
||||||
val accountProvider: AccountProvider,
|
val accountProviderInput: String,
|
||||||
val isAccountCreation: Boolean,
|
val isAccountCreation: Boolean,
|
||||||
val loginModeState: LoginModeState,
|
val loginModeState: LoginModeState,
|
||||||
|
val changeServerState: ChangeServerState,
|
||||||
val eventSink: (ConfirmAccountProviderEvents) -> Unit
|
val eventSink: (ConfirmAccountProviderEvents) -> Unit
|
||||||
) {
|
) {
|
||||||
val submitEnabled: Boolean
|
val submitEnabled: Boolean
|
||||||
get() = accountProvider.url.isNotEmpty() &&
|
get() = accountProviderInput.isNotBlank() &&
|
||||||
(loginModeState.loginMode is AsyncData.Uninitialized || loginModeState.loginMode is AsyncData.Loading)
|
loginModeState.loginMode is AsyncData.Uninitialized &&
|
||||||
|
changeServerState.changeServerAction is AsyncData.Uninitialized
|
||||||
|
|
||||||
|
val isLoading: Boolean
|
||||||
|
get() = loginModeState.loginMode is AsyncData.Loading ||
|
||||||
|
changeServerState.changeServerAction is AsyncData.Loading
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-4
@@ -9,8 +9,8 @@
|
|||||||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
import io.element.android.features.login.impl.changeserver.aChangeServerState
|
||||||
import io.element.android.features.login.impl.login.LoginModeState
|
import io.element.android.features.login.impl.login.LoginModeState
|
||||||
import io.element.android.features.login.impl.login.aLoginModeState
|
import io.element.android.features.login.impl.login.aLoginModeState
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
@@ -31,13 +31,15 @@ open class ConfirmAccountProviderStateProvider : PreviewParameterProvider<Confir
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun aConfirmAccountProviderState(
|
private fun aConfirmAccountProviderState(
|
||||||
accountProvider: AccountProvider = anAccountProvider(),
|
accountProviderInput: String = "matrix.org",
|
||||||
isAccountCreation: Boolean = false,
|
isAccountCreation: Boolean = false,
|
||||||
loginModeState: LoginModeState = aLoginModeState(),
|
loginModeState: LoginModeState = aLoginModeState(),
|
||||||
|
changeServerState: ChangeServerState = aChangeServerState(),
|
||||||
eventSink: (ConfirmAccountProviderEvents) -> Unit = {},
|
eventSink: (ConfirmAccountProviderEvents) -> Unit = {},
|
||||||
) = ConfirmAccountProviderState(
|
) = ConfirmAccountProviderState(
|
||||||
accountProvider = accountProvider,
|
accountProviderInput = accountProviderInput,
|
||||||
isAccountCreation = isAccountCreation,
|
isAccountCreation = isAccountCreation,
|
||||||
loginModeState = loginModeState,
|
loginModeState = loginModeState,
|
||||||
|
changeServerState = changeServerState,
|
||||||
eventSink = eventSink
|
eventSink = eventSink
|
||||||
)
|
)
|
||||||
|
|||||||
+82
-38
@@ -8,29 +8,38 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.changeserver.ChangeServerView
|
||||||
import io.element.android.features.login.impl.login.LoginModeEvent
|
import io.element.android.features.login.impl.login.LoginModeEvent
|
||||||
import io.element.android.features.login.impl.login.LoginModeView
|
import io.element.android.features.login.impl.login.LoginModeView
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
|
import io.element.android.libraries.designsystem.components.form.textFieldState
|
||||||
|
import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusNext
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
import io.element.android.libraries.designsystem.theme.components.TextField
|
||||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||||
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
@@ -44,14 +53,8 @@ fun ConfirmAccountProviderView(
|
|||||||
onNeedLoginPassword: () -> Unit,
|
onNeedLoginPassword: () -> Unit,
|
||||||
onLearnMoreClick: () -> Unit,
|
onLearnMoreClick: () -> Unit,
|
||||||
onCreateAccountContinue: (url: String) -> Unit,
|
onCreateAccountContinue: (url: String) -> Unit,
|
||||||
onChange: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val isLoading by remember(state.loginModeState.loginMode) {
|
|
||||||
derivedStateOf {
|
|
||||||
state.loginModeState.loginMode is AsyncData.Loading
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val eventSink = state.eventSink
|
val eventSink = state.eventSink
|
||||||
|
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
@@ -62,54 +65,96 @@ fun ConfirmAccountProviderView(
|
|||||||
iconStyle = BigIcon.Style.Default(CompoundIcons.UserProfileSolid()),
|
iconStyle = BigIcon.Style.Default(CompoundIcons.UserProfileSolid()),
|
||||||
title = stringResource(
|
title = stringResource(
|
||||||
id = if (state.isAccountCreation) {
|
id = if (state.isAccountCreation) {
|
||||||
R.string.screen_account_provider_signup_title
|
CommonStrings.screen_select_server_title_register
|
||||||
} else {
|
} else {
|
||||||
R.string.screen_account_provider_signin_title
|
CommonStrings.screen_select_server_title_login
|
||||||
},
|
}
|
||||||
state.accountProvider.title
|
|
||||||
),
|
),
|
||||||
subTitle = stringResource(
|
subTitle = null,
|
||||||
id = if (state.isAccountCreation) {
|
|
||||||
R.string.screen_account_provider_signup_subtitle
|
|
||||||
} else {
|
|
||||||
R.string.screen_account_provider_signin_subtitle
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
footer = {
|
footer = {
|
||||||
ButtonColumnMolecule {
|
ButtonColumnMolecule {
|
||||||
Button(
|
Button(
|
||||||
text = stringResource(id = CommonStrings.action_continue),
|
text = stringResource(id = CommonStrings.action_continue),
|
||||||
showProgress = isLoading,
|
showProgress = state.isLoading,
|
||||||
onClick = { eventSink.invoke(ConfirmAccountProviderEvents.Continue) },
|
onClick = { eventSink(ConfirmAccountProviderEvents.Continue) },
|
||||||
enabled = state.submitEnabled || isLoading,
|
enabled = state.submitEnabled && !state.isLoading,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.testTag(TestTags.loginContinue)
|
.testTag(TestTags.loginContinue)
|
||||||
)
|
)
|
||||||
TextButton(
|
|
||||||
text = stringResource(id = R.string.screen_account_provider_change),
|
|
||||||
onClick = onChange,
|
|
||||||
enabled = !isLoading,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.testTag(TestTags.loginChangeServer)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
var input by textFieldState(stateValue = state.accountProviderInput)
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
|
TextField(
|
||||||
|
value = input,
|
||||||
|
onValueChange = {
|
||||||
|
input = it
|
||||||
|
eventSink(ConfirmAccountProviderEvents.UserInputChanged(it))
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 40.dp)
|
||||||
|
.onTabOrEnterKeyFocusNext(focusManager)
|
||||||
|
.testTag(TestTags.changeServerServer),
|
||||||
|
label = stringResource(id = CommonStrings.screen_select_server_textfield_header),
|
||||||
|
supportingText = stringResource(
|
||||||
|
id = if (state.isAccountCreation) {
|
||||||
|
CommonStrings.screen_select_server_textfield_footer_register
|
||||||
|
} else {
|
||||||
|
CommonStrings.screen_select_server_textfield_footer_login
|
||||||
|
}
|
||||||
|
),
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Uri,
|
||||||
|
imeAction = ImeAction.Done,
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(onDone = {
|
||||||
|
eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
|
}),
|
||||||
|
singleLine = true,
|
||||||
|
trailingIcon = if (input.isNotEmpty()) {
|
||||||
|
{
|
||||||
|
Box(
|
||||||
|
Modifier.clickable(
|
||||||
|
onClickLabel = stringResource(CommonStrings.action_clear),
|
||||||
|
role = Role.Button,
|
||||||
|
) {
|
||||||
|
input = ""
|
||||||
|
eventSink(ConfirmAccountProviderEvents.UserInputChanged(""))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = CompoundIcons.Close(),
|
||||||
|
contentDescription = stringResource(CommonStrings.action_clear)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renders the account-provider validation errors, progress and permission dialogs.
|
||||||
|
// The successful validation itself is observed by the presenter, which then proceeds with the login flow.
|
||||||
|
ChangeServerView(
|
||||||
|
state = state.changeServerState,
|
||||||
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
|
onSuccess = {},
|
||||||
|
)
|
||||||
|
|
||||||
LoginModeView(
|
LoginModeView(
|
||||||
loginMode = state.loginModeState.loginMode,
|
loginMode = state.loginModeState.loginMode,
|
||||||
onClearError = {
|
onClearError = { eventSink(ConfirmAccountProviderEvents.ClearError) },
|
||||||
eventSink(ConfirmAccountProviderEvents.ClearError)
|
|
||||||
},
|
|
||||||
onLearnMoreClick = onLearnMoreClick,
|
onLearnMoreClick = onLearnMoreClick,
|
||||||
onOAuthDetails = onOAuthDetails,
|
onOAuthDetails = onOAuthDetails,
|
||||||
onNeedLoginPassword = onNeedLoginPassword,
|
onNeedLoginPassword = onNeedLoginPassword,
|
||||||
onCreateAccountContinue = onCreateAccountContinue,
|
onCreateAccountContinue = onCreateAccountContinue,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
LocalNetworkPermissionDialogView(
|
LocalNetworkPermissionDialogView(
|
||||||
dialog = state.loginModeState.localNetworkPermissionDialog,
|
dialog = state.loginModeState.localNetworkPermissionDialog,
|
||||||
@@ -133,6 +178,5 @@ internal fun ConfirmAccountProviderViewPreview(
|
|||||||
onNeedLoginPassword = {},
|
onNeedLoginPassword = {},
|
||||||
onCreateAccountContinue = {},
|
onCreateAccountContinue = {},
|
||||||
onLearnMoreClick = {},
|
onLearnMoreClick = {},
|
||||||
onChange = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-70
@@ -8,15 +8,21 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||||
|
|
||||||
|
import app.cash.turbine.ReceiveTurbine
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.appconfig.AuthenticationConfig
|
import io.element.android.appconfig.AuthenticationConfig
|
||||||
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
|
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||||
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.accountprovider.anAccountProviderDataSource
|
import io.element.android.features.login.impl.accountprovider.anAccountProviderDataSource
|
||||||
|
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
|
||||||
|
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||||
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.FakeWebClientUrlForAuthenticationRetriever
|
||||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
||||||
|
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||||
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
|
||||||
@@ -25,6 +31,9 @@ import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
|||||||
import io.element.android.libraries.oauth.api.OAuthAction
|
import io.element.android.libraries.oauth.api.OAuthAction
|
||||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||||
import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
|
import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
|
||||||
|
import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor
|
||||||
|
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
||||||
|
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.test
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
@@ -42,7 +51,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isAccountCreation).isFalse()
|
assertThat(initialState.isAccountCreation).isFalse()
|
||||||
assertThat(initialState.submitEnabled).isTrue()
|
assertThat(initialState.submitEnabled).isTrue()
|
||||||
assertThat(initialState.accountProvider.url).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
assertThat(initialState.accountProviderInput).isEqualTo(AuthenticationConfig.MATRIX_ORG_URL)
|
||||||
assertThat(initialState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
assertThat(initialState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,13 +69,9 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
|
||||||
assertThat(loadingState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
|
||||||
val successState = awaitItem()
|
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
|
||||||
assertThat(successState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.PasswordLogin)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,13 +88,9 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
|
||||||
assertThat(loadingState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
|
||||||
val successState = awaitItem()
|
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
|
||||||
assertThat(successState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,17 +109,13 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
|
||||||
assertThat(loadingState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
|
||||||
val successState = awaitItem()
|
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
|
||||||
assertThat(successState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
authenticationService.givenOAuthCancelError(AN_EXCEPTION)
|
authenticationService.givenOAuthCancelError(AN_EXCEPTION)
|
||||||
defaultOAuthActionFlow.post(OAuthAction.GoBack())
|
defaultOAuthActionFlow.post(OAuthAction.GoBack())
|
||||||
val cancelFailureState = awaitItem()
|
val cancelFailureState = awaitLoginMode { it is AsyncData.Failure }
|
||||||
assertThat(cancelFailureState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(cancelFailureState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,16 +134,12 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
|
||||||
assertThat(loadingState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
|
||||||
val successState = awaitItem()
|
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
|
||||||
assertThat(successState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
defaultOAuthActionFlow.post(OAuthAction.GoBack())
|
defaultOAuthActionFlow.post(OAuthAction.GoBack())
|
||||||
val cancelFinalState = awaitItem()
|
val cancelFinalState = awaitLoginMode { it is AsyncData.Uninitialized }
|
||||||
assertThat(cancelFinalState.loginModeState.loginMode).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(cancelFinalState.loginModeState.loginMode).isInstanceOf(AsyncData.Uninitialized::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,12 +158,11 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
awaitLoginMode { it is AsyncData.Loading }
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
|
||||||
assertThat(loadingState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
|
||||||
defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true))
|
defaultOAuthActionFlow.post(OAuthAction.GoBack(toUnblock = true))
|
||||||
val cancelFinalState = awaitItem()
|
val cancelFinalState = awaitLoginMode { it is AsyncData.Uninitialized }
|
||||||
assertThat(cancelFinalState.loginModeState.loginMode).isInstanceOf(AsyncData.Uninitialized::class.java)
|
assertThat(cancelFinalState.loginModeState.loginMode).isInstanceOf(AsyncData.Uninitialized::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,19 +181,13 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
|
||||||
assertThat(loadingState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
|
||||||
val successState = awaitItem()
|
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
|
||||||
assertThat(successState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
authenticationService.givenLoginError(AN_EXCEPTION)
|
authenticationService.givenLoginError(AN_EXCEPTION)
|
||||||
defaultOAuthActionFlow.post(OAuthAction.Success("aUrl"))
|
defaultOAuthActionFlow.post(OAuthAction.Success("aUrl"))
|
||||||
val cancelLoadingState = awaitItem()
|
val cancelFailureState = awaitLoginMode { it is AsyncData.Failure }
|
||||||
assertThat(cancelLoadingState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
|
||||||
val cancelFailureState = awaitItem()
|
|
||||||
assertThat(cancelFailureState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
assertThat(cancelFailureState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,16 +206,12 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
val loadingState = awaitItem()
|
val successState = awaitLoginMode { it is AsyncData.Success }
|
||||||
assertThat(loadingState.submitEnabled).isTrue()
|
|
||||||
assertThat(loadingState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
|
||||||
val successState = awaitItem()
|
|
||||||
assertThat(successState.submitEnabled).isFalse()
|
|
||||||
assertThat(successState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(successState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
defaultOidcActionFlow.post(OAuthAction.Success("aUrl"))
|
defaultOidcActionFlow.post(OAuthAction.Success("aUrl"))
|
||||||
val successSuccessState = awaitItem()
|
val successSuccessState = awaitLoginMode { it is AsyncData.Loading }
|
||||||
assertThat(successSuccessState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(successSuccessState.loginModeState.loginMode).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,10 +228,10 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||||
skipItems(1) // Loading
|
// The account provider validation fails, so the login is never attempted.
|
||||||
val failureState = awaitItem()
|
val failureState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
|
||||||
assertThat(failureState.submitEnabled).isFalse()
|
assertThat(failureState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||||
assertThat(failureState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,18 +248,15 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
||||||
// Submit will return an error
|
// Submit will return an error while validating the account provider
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
|
|
||||||
skipItems(1) // Loading
|
|
||||||
|
|
||||||
// Check an error was returned
|
// Check an error was returned
|
||||||
val submittedState = awaitItem()
|
val submittedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Failure }
|
||||||
assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Failure::class.java)
|
|
||||||
|
|
||||||
// Assert the error is then cleared
|
// Assert the error is then cleared
|
||||||
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
|
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
|
||||||
val clearedState = awaitItem()
|
val clearedState = awaitState { it.changeServerState.changeServerAction is AsyncData.Uninitialized }
|
||||||
assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,7 +265,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
fun `present - confirm account creation without oidc and without url generates an error`() = runTest {
|
fun `present - confirm account creation without oidc and without url generates an error`() = runTest {
|
||||||
val authenticationService = FakeMatrixAuthenticationService(
|
val authenticationService = FakeMatrixAuthenticationService(
|
||||||
setHomeserverResult = {
|
setHomeserverResult = {
|
||||||
Result.success(aMatrixHomeServerDetails())
|
Result.success(aMatrixHomeServerDetails(supportsPasswordLogin = true))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
val presenter = createConfirmAccountProviderPresenter(
|
val presenter = createConfirmAccountProviderPresenter(
|
||||||
@@ -299,14 +278,14 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
skipItems(1) // Loading
|
|
||||||
// Check an error was returned
|
// Check an error was returned
|
||||||
val submittedState = awaitItem()
|
val submittedState = awaitLoginMode { it is AsyncData.Failure }
|
||||||
assertThat(submittedState.loginModeState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
|
assertThat(submittedState.loginModeState.loginMode.errorOrNull()).isInstanceOf(AccountCreationNotSupported::class.java)
|
||||||
// Assert the error is then cleared
|
// Assert the error is then cleared
|
||||||
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
|
submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
|
||||||
val clearedState = awaitItem()
|
val clearedState = awaitLoginMode { it is AsyncData.Uninitialized }
|
||||||
assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
assertThat(clearedState.loginModeState.loginMode).isEqualTo(AsyncData.Uninitialized)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,10 +303,9 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
skipItems(1) // Loading
|
val submittedState = awaitLoginMode { it is AsyncData.Success }
|
||||||
val submittedState = awaitItem()
|
|
||||||
assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,10 +325,9 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
skipItems(1) // Loading
|
val submittedState = awaitLoginMode { it is AsyncData.Success }
|
||||||
val submittedState = awaitItem()
|
|
||||||
assertThat(submittedState.loginModeState.loginMode).isInstanceOf(AsyncData.Success::class.java)
|
|
||||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isInstanceOf(LoginMode.OAuth::class.java)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +336,7 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
val aUrl = "aUrl"
|
val aUrl = "aUrl"
|
||||||
val authenticationService = FakeMatrixAuthenticationService(
|
val authenticationService = FakeMatrixAuthenticationService(
|
||||||
setHomeserverResult = {
|
setHomeserverResult = {
|
||||||
Result.success(aMatrixHomeServerDetails())
|
Result.success(aMatrixHomeServerDetails(supportsPasswordLogin = true))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
val presenter = createConfirmAccountProviderPresenter(
|
val presenter = createConfirmAccountProviderPresenter(
|
||||||
@@ -370,9 +347,26 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
presenter.test {
|
presenter.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
initialState.eventSink(ConfirmAccountProviderEvents.Continue)
|
||||||
skipItems(1) // Loading
|
val submittedState = awaitLoginMode { it is AsyncData.Success }
|
||||||
val submittedState = awaitItem()
|
|
||||||
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.AccountCreation(aUrl))
|
assertThat(submittedState.loginModeState.loginMode.dataOrNull()).isEqualTo(LoginMode.AccountCreation(aUrl))
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaits until the emitted state's login mode matches [predicate], skipping the intermediate
|
||||||
|
* account-provider validation states, and returns that state.
|
||||||
|
*/
|
||||||
|
private suspend fun ReceiveTurbine<ConfirmAccountProviderState>.awaitLoginMode(
|
||||||
|
predicate: (AsyncData<LoginMode>) -> Boolean,
|
||||||
|
): ConfirmAccountProviderState = awaitState { predicate(it.loginModeState.loginMode) }
|
||||||
|
|
||||||
|
private suspend fun ReceiveTurbine<ConfirmAccountProviderState>.awaitState(
|
||||||
|
predicate: (ConfirmAccountProviderState) -> Boolean,
|
||||||
|
): ConfirmAccountProviderState {
|
||||||
|
while (true) {
|
||||||
|
val item = awaitItem()
|
||||||
|
if (predicate(item)) return item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,5 +384,19 @@ class ConfirmAccountProviderPresenterTest {
|
|||||||
oAuthActionFlow = defaultOAuthActionFlow,
|
oAuthActionFlow = defaultOAuthActionFlow,
|
||||||
webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
|
webClientUrlForAuthenticationRetriever = webClientUrlForAuthenticationRetriever,
|
||||||
),
|
),
|
||||||
|
changeServerPresenter = ChangeServerPresenter(
|
||||||
|
authenticationService = matrixAuthenticationService,
|
||||||
|
accountProviderDataSource = accountProviderDataSource,
|
||||||
|
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
|
||||||
|
enterpriseService = FakeEnterpriseService(
|
||||||
|
isAllowedToConnectToHomeserverResult = { true },
|
||||||
|
),
|
||||||
|
wellknownRetriever = FakeWellknownRetriever(),
|
||||||
|
),
|
||||||
|
localNetworkPermissionGate = LocalNetworkPermissionGate(
|
||||||
|
advisor = FakeLocalNetworkPermissionAdvisor(),
|
||||||
|
permissionsPresenterFactory = FakePermissionsPresenterFactory(FakePermissionsPresenter()),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,9 @@
|
|||||||
<string name="a11y_state_collapsed">"Collapsed"</string>
|
<string name="a11y_state_collapsed">"Collapsed"</string>
|
||||||
<string name="a11y_active">Active</string>
|
<string name="a11y_active">Active</string>
|
||||||
<string name="a11y_inactive">Inactive</string>
|
<string name="a11y_inactive">Inactive</string>
|
||||||
|
<string name="screen_select_server_title_login">"Enter your account provider"</string>
|
||||||
|
<string name="screen_select_server_title_register">"Choose an account provider"</string>
|
||||||
|
<string name="screen_select_server_textfield_header">"Account provider"</string>
|
||||||
|
<string name="screen_select_server_textfield_footer_login">"If your account is provided by your company or a community, then change the account provider (example: companyname.com)."</string>
|
||||||
|
<string name="screen_select_server_textfield_footer_register">"Create an account on matrix.org for free. If you want to create an account with a different provider, then change it accordingly (example: communityname.org)."</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user