Rename nodes and organize by package into screens subpackage for clarity

This commit is contained in:
Benoit Marty 2023-06-09 17:34:14 +02:00
parent 06297bb792
commit dc064069ba
52 changed files with 450 additions and 476 deletions

View file

@ -32,14 +32,14 @@ import com.bumble.appyx.navmodel.backstack.operation.singleTop
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.login.impl.accountprovider.AccountProviderNode import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.changeaccountprovider.ChangeAccountProviderNode
import io.element.android.features.login.impl.changeaccountprovider.form.ChangeAccountProviderFormNode
import io.element.android.features.login.impl.datasource.AccountProviderDataSource
import io.element.android.features.login.impl.loginpassword.LoginPasswordNode
import io.element.android.features.login.impl.oidc.CustomTabAvailabilityChecker import io.element.android.features.login.impl.oidc.CustomTabAvailabilityChecker
import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler import io.element.android.features.login.impl.oidc.customtab.CustomTabHandler
import io.element.android.features.login.impl.oidc.webview.OidcNode import io.element.android.features.login.impl.oidc.webview.OidcNode
import io.element.android.features.login.impl.screens.changeaccountprovider.ChangeAccountProviderNode
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderNode
import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordNode
import io.element.android.features.login.impl.screens.searchaccountprovider.SearchAccountProviderNode
import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
@ -59,7 +59,7 @@ class LoginFlowNode @AssistedInject constructor(
private val accountProviderDataSource: AccountProviderDataSource, private val accountProviderDataSource: AccountProviderDataSource,
) : BackstackNode<LoginFlowNode.NavTarget>( ) : BackstackNode<LoginFlowNode.NavTarget>(
backstack = BackStack( backstack = BackStack(
initialElement = NavTarget.AccountProvider, // NavTarget.Root, initialElement = NavTarget.ConfirmAccountProvider,
savedStateMap = buildContext.savedStateMap, savedStateMap = buildContext.savedStateMap,
), ),
buildContext = buildContext, buildContext = buildContext,
@ -76,16 +76,16 @@ class LoginFlowNode @AssistedInject constructor(
sealed interface NavTarget : Parcelable { sealed interface NavTarget : Parcelable {
@Parcelize @Parcelize
object AccountProvider : NavTarget object ConfirmAccountProvider : NavTarget
@Parcelize @Parcelize
object ChangeAccountProvider : NavTarget object ChangeAccountProvider : NavTarget
@Parcelize @Parcelize
object ChangeAccountProviderForm : NavTarget object SearchAccountProvider : NavTarget
@Parcelize @Parcelize
object LoginPasswordForm : NavTarget object LoginPassword : NavTarget
@Parcelize @Parcelize
data class OidcView(val oidcDetails: OidcDetails) : NavTarget data class OidcView(val oidcDetails: OidcDetails) : NavTarget
@ -93,15 +93,11 @@ class LoginFlowNode @AssistedInject constructor(
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
return when (navTarget) { return when (navTarget) {
is NavTarget.OidcView -> { NavTarget.ConfirmAccountProvider -> {
val input = OidcNode.Inputs(navTarget.oidcDetails) val inputs = ConfirmAccountProviderNode.Inputs(
createNode<OidcNode>(buildContext, plugins = listOf(input))
}
NavTarget.AccountProvider -> {
val inputs = AccountProviderNode.Inputs(
isAccountCreation = inputs.isAccountCreation isAccountCreation = inputs.isAccountCreation
) )
val callback = object : AccountProviderNode.Callback { val callback = object : ConfirmAccountProviderNode.Callback {
override fun onOidcDetails(oidcDetails: OidcDetails) { override fun onOidcDetails(oidcDetails: OidcDetails) {
if (customTabAvailabilityChecker.supportCustomTab()) { if (customTabAvailabilityChecker.supportCustomTab()) {
// In this case open a Chrome Custom tab // In this case open a Chrome Custom tab
@ -113,42 +109,46 @@ class LoginFlowNode @AssistedInject constructor(
} }
override fun onLoginPasswordNeeded() { override fun onLoginPasswordNeeded() {
backstack.push(NavTarget.LoginPasswordForm) backstack.push(NavTarget.LoginPassword)
} }
override fun onChangeAccountProvider() { override fun onChangeAccountProvider() {
backstack.push(NavTarget.ChangeAccountProvider) backstack.push(NavTarget.ChangeAccountProvider)
} }
} }
createNode<AccountProviderNode>(buildContext, plugins = listOf(inputs, callback)) createNode<ConfirmAccountProviderNode>(buildContext, plugins = listOf(inputs, callback))
} }
NavTarget.ChangeAccountProvider -> { NavTarget.ChangeAccountProvider -> {
val callback = object : ChangeAccountProviderNode.Callback { val callback = object : ChangeAccountProviderNode.Callback {
override fun onDone() { override fun onDone() {
// Go back to the Account Provider screen // Go back to the Account Provider screen
backstack.singleTop(NavTarget.AccountProvider) backstack.singleTop(NavTarget.ConfirmAccountProvider)
} }
override fun onOtherClicked() { override fun onOtherClicked() {
backstack.push(NavTarget.ChangeAccountProviderForm) backstack.push(NavTarget.SearchAccountProvider)
} }
} }
createNode<ChangeAccountProviderNode>(buildContext, plugins = listOf(callback)) createNode<ChangeAccountProviderNode>(buildContext, plugins = listOf(callback))
} }
NavTarget.ChangeAccountProviderForm -> { NavTarget.SearchAccountProvider -> {
val callback = object : ChangeAccountProviderFormNode.Callback { val callback = object : SearchAccountProviderNode.Callback {
override fun onDone() { override fun onDone() {
// Go back to the Account Provider screen // Go back to the Account Provider screen
backstack.singleTop(NavTarget.AccountProvider) backstack.singleTop(NavTarget.ConfirmAccountProvider)
} }
} }
createNode<ChangeAccountProviderFormNode>(buildContext, plugins = listOf(callback)) createNode<SearchAccountProviderNode>(buildContext, plugins = listOf(callback))
} }
NavTarget.LoginPasswordForm -> { NavTarget.LoginPassword -> {
createNode<LoginPasswordNode>(buildContext, plugins = listOf()) createNode<LoginPasswordNode>(buildContext, plugins = listOf())
} }
is NavTarget.OidcView -> {
val input = OidcNode.Inputs(navTarget.oidcDetails)
createNode<OidcNode>(buildContext, plugins = listOf(input))
}
} }
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider.item package io.element.android.features.login.impl.accountprovider
data class AccountProvider constructor( data class AccountProvider constructor(
val title: String, val title: String,

View file

@ -14,9 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.datasource package io.element.android.features.login.impl.accountprovider
import io.element.android.features.login.impl.accountprovider.item.AccountProvider
import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.features.login.impl.util.defaultAccountProvider
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider.item package io.element.android.features.login.impl.accountprovider
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider

View file

@ -16,146 +16,117 @@
package io.element.android.features.login.impl.accountprovider package io.element.android.features.login.impl.accountprovider
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf import androidx.compose.ui.Alignment
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
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.features.login.impl.R import io.element.android.features.login.impl.R
import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.architecture.Async import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.components.button.ButtonWithProgress
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
/**
* https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=604-60817
*/
@Composable @Composable
fun AccountProviderView( fun AccountProviderView(
state: AccountProviderState, item: AccountProvider,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onOidcDetails: (OidcDetails) -> Unit = {}, onClick: () -> Unit,
onLoginPasswordNeeded: () -> Unit = {},
onLearnMoreClicked: () -> Unit = {},
onChange: () -> Unit = {},
) { ) {
val isLoading by remember(state.loginFlow) { Column(modifier = modifier
derivedStateOf { .fillMaxWidth()
state.loginFlow is Async.Loading .clickable { onClick() }) {
} Divider()
} Column(
val eventSink = state.eventSink modifier = Modifier
.fillMaxWidth()
HeaderFooterPage( .padding(vertical = 4.dp, horizontal = 16.dp)
modifier = modifier, ) {
header = { Row(
IconTitleSubtitleMolecule( modifier = Modifier
modifier = Modifier.padding(top = 60.dp), .fillMaxWidth()
iconImageVector = Icons.Filled.AccountCircle, .heightIn(min = 44.dp),
title = stringResource( verticalAlignment = Alignment.CenterVertically
id = if (state.isAccountCreation) { ) {
R.string.screen_account_provider_signup_title if (item.isMatrixOrg) {
} else { RoundedIconAtom(
R.string.screen_account_provider_signin_title size = RoundedIconAtomSize.Medium,
}, resourceId = R.drawable.ic_matrix,
state.accountProvider.title tint = Color.Unspecified,
), )
subTitle = stringResource( } else {
id = if (state.isAccountCreation) { RoundedIconAtom(
R.string.screen_account_provider_signup_subtitle size = RoundedIconAtomSize.Medium,
} else { imageVector = Icons.Filled.Search,
// Use same value for now. tint = MaterialTheme.colorScheme.primary,
R.string.screen_account_provider_signup_subtitle )
}, }
) Text(
)
},
footer = {
ButtonColumnMolecule {
ButtonWithProgress(
text = stringResource(id = R.string.screen_account_provider_continue),
showProgress = isLoading,
onClick = { eventSink.invoke(AccountProviderEvents.Continue) },
enabled = state.submitEnabled,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .padding(start = 16.dp)
.testTag(TestTags.loginContinue) .weight(1f),
text = item.title,
style = ElementTextStyles.Regular.headline.copy(textAlign = TextAlign.Start),
color = MaterialTheme.colorScheme.primary,
) )
TextButton( if (item.isPublic) {
onClick = { Icon(
onChange() modifier = Modifier
}, .padding(start = 10.dp)
enabled = true, .size(16.dp),
resourceId = R.drawable.ic_public,
contentDescription = null,
tint = Color.Unspecified,
)
}
}
if (item.subtitle != null) {
Text(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .padding(start = 46.dp, bottom = 12.dp, end = 26.dp),
.testTag(TestTags.loginChangeServer) text = item.subtitle,
) { style = ElementTextStyles.Regular.subheadline.copy(textAlign = TextAlign.Start),
Text(text = stringResource(id = R.string.screen_account_provider_change)) color = MaterialTheme.colorScheme.secondary,
} )
} }
} }
) {
when (state.loginFlow) {
is Async.Failure -> {
when (val error = state.loginFlow.error) {
is ChangeServerError.InlineErrorMessage -> {
ErrorDialog(
content = error.message(),
onDismiss = {
eventSink.invoke(AccountProviderEvents.ClearError)
}
)
}
is ChangeServerError.SlidingSyncAlert -> {
SlidingSyncNotSupportedDialog(onLearnMoreClicked = {
onLearnMoreClicked()
eventSink(AccountProviderEvents.ClearError)
}, onDismiss = {
eventSink(AccountProviderEvents.ClearError)
})
}
}
}
is Async.Loading -> Unit // The Continue button shows the loading state
is Async.Success -> {
when (val loginFlowState = state.loginFlow.state) {
is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails)
LoginFlow.PasswordLogin -> onLoginPasswordNeeded()
}
}
Async.Uninitialized -> Unit
}
} }
} }
@Preview @Preview
@Composable @Composable
fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderStateProvider::class) state: AccountProviderState) = fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) =
ElementPreviewLight { ContentToPreview(state) } ElementPreviewLight { ContentToPreview(item) }
@Preview @Preview
@Composable @Composable
fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderStateProvider::class) state: AccountProviderState) = fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) =
ElementPreviewDark { ContentToPreview(state) } ElementPreviewDark { ContentToPreview(item) }
@Composable @Composable
private fun ContentToPreview(state: AccountProviderState) { private fun ContentToPreview(item: AccountProvider) {
AccountProviderView( AccountProviderView(
state = state, item = item,
onClick = { }
) )
} }

View file

@ -1,132 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.login.impl.accountprovider.item
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.features.login.impl.R
import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Divider
import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text
/**
* https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=604-60817
*/
@Composable
fun AccountProviderView(
item: AccountProvider,
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
Column(modifier = modifier
.fillMaxWidth()
.clickable { onClick() }) {
Divider()
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp, horizontal = 16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 44.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (item.isMatrixOrg) {
RoundedIconAtom(
size = RoundedIconAtomSize.Medium,
resourceId = R.drawable.ic_matrix,
tint = Color.Unspecified,
)
} else {
RoundedIconAtom(
size = RoundedIconAtomSize.Medium,
imageVector = Icons.Filled.Search,
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
modifier = Modifier
.padding(start = 16.dp)
.weight(1f),
text = item.title,
style = ElementTextStyles.Regular.headline.copy(textAlign = TextAlign.Start),
color = MaterialTheme.colorScheme.primary,
)
if (item.isPublic) {
Icon(
modifier = Modifier
.padding(start = 10.dp)
.size(16.dp),
resourceId = R.drawable.ic_public,
contentDescription = null,
tint = Color.Unspecified,
)
}
}
if (item.subtitle != null) {
Text(
modifier = Modifier
.padding(start = 46.dp, bottom = 12.dp, end = 26.dp),
text = item.subtitle,
style = ElementTextStyles.Regular.subheadline.copy(textAlign = TextAlign.Start),
color = MaterialTheme.colorScheme.secondary,
)
}
}
}
}
@Preview
@Composable
fun AccountProviderViewLightPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) =
ElementPreviewLight { ContentToPreview(item) }
@Preview
@Composable
fun AccountProviderViewDarkPreview(@PreviewParameter(AccountProviderProvider::class) item: AccountProvider) =
ElementPreviewDark { ContentToPreview(item) }
@Composable
private fun ContentToPreview(item: AccountProvider) {
AccountProviderView(
item = item,
onClick = { }
)
}

View file

@ -1,27 +0,0 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.login.impl.changeaccountprovider.form
import io.element.android.libraries.architecture.Async
import kotlinx.coroutines.flow.Flow
/**
* Resolve homeserver base on search terms.
*/
interface HomeserverResolver {
suspend fun resolve(userInput: String): Flow<Async<List<HomeserverData>>>
}

View file

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.common package io.element.android.features.login.impl.changeserver
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
sealed interface ChangeServerEvents { sealed interface ChangeServerEvents {
data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvents data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvents

View file

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.common package io.element.android.features.login.impl.changeserver
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.error.ChangeServerError
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.common package io.element.android.features.login.impl.changeserver
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.common package io.element.android.features.login.impl.changeserver
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.common package io.element.android.features.login.impl.changeserver
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.login.impl.accountprovider.SlidingSyncNotSupportedDialog import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.error.ChangeServerError
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.components.ProgressDialog import io.element.android.libraries.designsystem.components.ProgressDialog

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.changeserver.resolver
data class HomeserverData constructor( data class HomeserverData constructor(
// The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url // The computed homeserver url, for which a wellknown file has been retrieved, or just a valid Url

View file

@ -14,17 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.changeserver.resolver
import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.login.impl.changeserver.resolver.network.WellknownRequest
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.core.uri.ensureProtocol
import io.element.android.libraries.core.uri.isValidUrl import io.element.android.libraries.core.uri.isValidUrl
import io.element.android.libraries.di.AppScope
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
@ -38,13 +36,12 @@ import javax.inject.Inject
/** /**
* Resolve homeserver base on search terms. * Resolve homeserver base on search terms.
*/ */
@ContributesBinding(AppScope::class) class HomeserverResolver @Inject constructor(
class DefaultHomeserverResolver @Inject constructor(
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val wellknownRequest: WellknownRequest, private val wellknownRequest: WellknownRequest,
) : HomeserverResolver { ) {
override suspend fun resolve(userInput: String): Flow<Async<List<HomeserverData>>> = flow { suspend fun resolve(userInput: String): Flow<Async<List<HomeserverData>>> = flow {
val flowContext = currentCoroutineContext() val flowContext = currentCoroutineContext()
emit(Async.Uninitialized) emit(Async.Uninitialized)
// Debounce // Debounce

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form.network package io.element.android.features.login.impl.changeserver.resolver.network
import com.squareup.anvil.annotations.ContributesBinding import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form.network package io.element.android.features.login.impl.changeserver.resolver.network
import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.bool.orFalse
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form.network package io.element.android.features.login.impl.changeserver.resolver.network
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form.network package io.element.android.features.login.impl.changeserver.resolver.network
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form.network package io.element.android.features.login.impl.changeserver.resolver.network
import retrofit2.http.GET import retrofit2.http.GET

View file

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form.network package io.element.android.features.login.impl.changeserver.resolver.network
interface WellknownRequest { interface WellknownRequest {
/** /**

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider package io.element.android.features.login.impl.dialogs
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider package io.element.android.features.login.impl.screens.changeaccountprovider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider package io.element.android.features.login.impl.screens.changeaccountprovider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import javax.inject.Inject import javax.inject.Inject

View file

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider package io.element.android.features.login.impl.screens.changeaccountprovider
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerState import io.element.android.features.login.impl.changeserver.ChangeServerState
// Do not use default value, so no member get forgotten in the presenters. // Do not use default value, so no member get forgotten in the presenters.
data class ChangeAccountProviderState constructor( data class ChangeAccountProviderState constructor(

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider package io.element.android.features.login.impl.screens.changeaccountprovider
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.login.impl.accountprovider.item.anAccountProvider import io.element.android.features.login.impl.accountprovider.anAccountProvider
import io.element.android.features.login.impl.changeaccountprovider.common.aChangeServerState import io.element.android.features.login.impl.changeserver.aChangeServerState
open class ChangeAccountProviderStateProvider : PreviewParameterProvider<ChangeAccountProviderState> { open class ChangeAccountProviderStateProvider : PreviewParameterProvider<ChangeAccountProviderState> {
override val values: Sequence<ChangeAccountProviderState> override val values: Sequence<ChangeAccountProviderState>

View file

@ -16,7 +16,7 @@
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
package io.element.android.features.login.impl.changeaccountprovider package io.element.android.features.login.impl.screens.changeaccountprovider
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -40,10 +40,10 @@ import androidx.compose.ui.tooling.preview.Preview
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.features.login.impl.R import io.element.android.features.login.impl.R
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.accountprovider.item.AccountProviderView import io.element.android.features.login.impl.accountprovider.AccountProviderView
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerEvents import io.element.android.features.login.impl.changeserver.ChangeServerEvents
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerView import io.element.android.features.login.impl.changeserver.ChangeServerView
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark

View file

@ -14,9 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider package io.element.android.features.login.impl.screens.confirmaccountprovider
sealed interface AccountProviderEvents { sealed interface ConfirmAccountProviderEvents {
object Continue : AccountProviderEvents object Continue : ConfirmAccountProviderEvents
object ClearError : AccountProviderEvents object ClearError : ConfirmAccountProviderEvents
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider package io.element.android.features.login.impl.screens.confirmaccountprovider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -33,10 +33,10 @@ import io.element.android.libraries.di.AppScope
import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.auth.OidcDetails
@ContributesNode(AppScope::class) @ContributesNode(AppScope::class)
class AccountProviderNode @AssistedInject constructor( class ConfirmAccountProviderNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
presenterFactory: AccountProviderPresenter.Factory, presenterFactory: ConfirmAccountProviderPresenter.Factory,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
data class Inputs( data class Inputs(
@ -45,7 +45,7 @@ class AccountProviderNode @AssistedInject constructor(
private val inputs: Inputs = inputs() private val inputs: Inputs = inputs()
private val presenter = presenterFactory.create( private val presenter = presenterFactory.create(
AccountProviderPresenterParams( ConfirmAccountProviderPresenter.Params(
isAccountCreation = inputs.isAccountCreation, isAccountCreation = inputs.isAccountCreation,
) )
) )
@ -72,7 +72,7 @@ class AccountProviderNode @AssistedInject constructor(
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
val context = LocalContext.current val context = LocalContext.current
AccountProviderView( ConfirmAccountProviderView(
state = state, state = state,
modifier = modifier, modifier = modifier,
onOidcDetails = ::onOidcDetails, onOidcDetails = ::onOidcDetails,

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider package io.element.android.features.login.impl.screens.confirmaccountprovider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
@ -26,7 +26,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.error.ChangeServerError import io.element.android.features.login.impl.error.ChangeServerError
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
@ -37,23 +37,23 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.net.URL import java.net.URL
data class AccountProviderPresenterParams( class ConfirmAccountProviderPresenter @AssistedInject constructor(
val isAccountCreation: Boolean, @Assisted private val params: Params,
)
class AccountProviderPresenter @AssistedInject constructor(
@Assisted private val params: AccountProviderPresenterParams,
private val accountProviderDataSource: AccountProviderDataSource, private val accountProviderDataSource: AccountProviderDataSource,
private val authenticationService: MatrixAuthenticationService private val authenticationService: MatrixAuthenticationService
) : Presenter<AccountProviderState> { ) : Presenter<ConfirmAccountProviderState> {
data class Params(
val isAccountCreation: Boolean,
)
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(params: AccountProviderPresenterParams): AccountProviderPresenter fun create(params: Params): ConfirmAccountProviderPresenter
} }
@Composable @Composable
override fun present(): AccountProviderState { override fun present(): ConfirmAccountProviderState {
val accountProvider by accountProviderDataSource.flow().collectAsState() val accountProvider by accountProviderDataSource.flow().collectAsState()
val localCoroutineScope = rememberCoroutineScope() val localCoroutineScope = rememberCoroutineScope()
@ -61,16 +61,16 @@ class AccountProviderPresenter @AssistedInject constructor(
mutableStateOf(Async.Uninitialized) mutableStateOf(Async.Uninitialized)
} }
fun handleEvents(event: AccountProviderEvents) { fun handleEvents(event: ConfirmAccountProviderEvents) {
when (event) { when (event) {
AccountProviderEvents.Continue -> { ConfirmAccountProviderEvents.Continue -> {
localCoroutineScope.submit(accountProvider.title, loginFlowAction) localCoroutineScope.submit(accountProvider.title, loginFlowAction)
} }
AccountProviderEvents.ClearError -> loginFlowAction.value = Async.Uninitialized ConfirmAccountProviderEvents.ClearError -> loginFlowAction.value = Async.Uninitialized
} }
} }
return AccountProviderState( return ConfirmAccountProviderState(
accountProvider = accountProvider, accountProvider = accountProvider,
isAccountCreation = params.isAccountCreation, isAccountCreation = params.isAccountCreation,
loginFlow = loginFlowAction.value, loginFlow = loginFlowAction.value,

View file

@ -14,18 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider package io.element.android.features.login.impl.screens.confirmaccountprovider
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.auth.OidcDetails import io.element.android.libraries.matrix.api.auth.OidcDetails
// Do not use default value, so no member get forgotten in the presenters. // Do not use default value, so no member get forgotten in the presenters.
data class AccountProviderState( data class ConfirmAccountProviderState(
val accountProvider: AccountProvider, val accountProvider: AccountProvider,
val isAccountCreation: Boolean, val isAccountCreation: Boolean,
val loginFlow: Async<LoginFlow>, val loginFlow: Async<LoginFlow>,
val eventSink: (AccountProviderEvents) -> Unit val eventSink: (ConfirmAccountProviderEvents) -> Unit
) { ) {
val submitEnabled: Boolean get() = accountProvider.title.isNotEmpty() && (loginFlow is Async.Uninitialized || loginFlow is Async.Loading) val submitEnabled: Boolean get() = accountProvider.title.isNotEmpty() && (loginFlow is Async.Uninitialized || loginFlow is Async.Loading)
} }

View file

@ -14,21 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider 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.item.anAccountProvider import io.element.android.features.login.impl.accountprovider.anAccountProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
open class AccountProviderStateProvider : PreviewParameterProvider<AccountProviderState> { open class ConfirmAccountProviderStateProvider : PreviewParameterProvider<ConfirmAccountProviderState> {
override val values: Sequence<AccountProviderState> override val values: Sequence<ConfirmAccountProviderState>
get() = sequenceOf( get() = sequenceOf(
anAccountProviderState(), aConfirmAccountProviderState(),
// Add other state here // Add other state here
) )
} }
fun anAccountProviderState() = AccountProviderState( fun aConfirmAccountProviderState() = ConfirmAccountProviderState(
accountProvider = anAccountProvider(), accountProvider = anAccountProvider(),
isAccountCreation = false, isAccountCreation = false,
loginFlow = Async.Uninitialized, loginFlow = Async.Uninitialized,

View file

@ -0,0 +1,162 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.login.impl.screens.confirmaccountprovider
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.features.login.impl.R
import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
import io.element.android.features.login.impl.error.ChangeServerError
import io.element.android.libraries.architecture.Async
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.pages.HeaderFooterPage
import io.element.android.libraries.designsystem.components.button.ButtonWithProgress
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TextButton
import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.testtags.testTag
@Composable
fun ConfirmAccountProviderView(
state: ConfirmAccountProviderState,
modifier: Modifier = Modifier,
onOidcDetails: (OidcDetails) -> Unit = {},
onLoginPasswordNeeded: () -> Unit = {},
onLearnMoreClicked: () -> Unit = {},
onChange: () -> Unit = {},
) {
val isLoading by remember(state.loginFlow) {
derivedStateOf {
state.loginFlow is Async.Loading
}
}
val eventSink = state.eventSink
HeaderFooterPage(
modifier = modifier,
header = {
IconTitleSubtitleMolecule(
modifier = Modifier.padding(top = 60.dp),
iconImageVector = Icons.Filled.AccountCircle,
title = stringResource(
id = if (state.isAccountCreation) {
R.string.screen_account_provider_signup_title
} else {
R.string.screen_account_provider_signin_title
},
state.accountProvider.title
),
subTitle = stringResource(
id = if (state.isAccountCreation) {
R.string.screen_account_provider_signup_subtitle
} else {
// Use same value for now.
R.string.screen_account_provider_signup_subtitle
},
)
)
},
footer = {
ButtonColumnMolecule {
ButtonWithProgress(
text = stringResource(id = R.string.screen_account_provider_continue),
showProgress = isLoading,
onClick = { eventSink.invoke(ConfirmAccountProviderEvents.Continue) },
enabled = state.submitEnabled,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginContinue)
)
TextButton(
onClick = {
onChange()
},
enabled = true,
modifier = Modifier
.fillMaxWidth()
.testTag(TestTags.loginChangeServer)
) {
Text(text = stringResource(id = R.string.screen_account_provider_change))
}
}
}
) {
when (state.loginFlow) {
is Async.Failure -> {
when (val error = state.loginFlow.error) {
is ChangeServerError.InlineErrorMessage -> {
ErrorDialog(
content = error.message(),
onDismiss = {
eventSink.invoke(ConfirmAccountProviderEvents.ClearError)
}
)
}
is ChangeServerError.SlidingSyncAlert -> {
SlidingSyncNotSupportedDialog(onLearnMoreClicked = {
onLearnMoreClicked()
eventSink(ConfirmAccountProviderEvents.ClearError)
}, onDismiss = {
eventSink(ConfirmAccountProviderEvents.ClearError)
})
}
}
}
is Async.Loading -> Unit // The Continue button shows the loading state
is Async.Success -> {
when (val loginFlowState = state.loginFlow.state) {
is LoginFlow.OidcFlow -> onOidcDetails(loginFlowState.oidcDetails)
LoginFlow.PasswordLogin -> onLoginPasswordNeeded()
}
}
Async.Uninitialized -> Unit
}
}
}
@Preview
@Composable
fun ConfirmAccountProviderViewLightPreview(@PreviewParameter(ConfirmAccountProviderStateProvider::class) state: ConfirmAccountProviderState) =
ElementPreviewLight { ContentToPreview(state) }
@Preview
@Composable
fun ConfirmAccountProviderViewDarkPreview(@PreviewParameter(ConfirmAccountProviderStateProvider::class) state: ConfirmAccountProviderState) =
ElementPreviewDark { ContentToPreview(state) }
@Composable
private fun ContentToPreview(state: ConfirmAccountProviderState) {
ConfirmAccountProviderView(
state = state,
)
}

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.loginpassword package io.element.android.features.login.impl.screens.loginpassword
sealed interface LoginPasswordEvents { sealed interface LoginPasswordEvents {
data class SetLogin(val login: String) : LoginPasswordEvents data class SetLogin(val login: String) : LoginPasswordEvents

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.loginpassword package io.element.android.features.login.impl.screens.loginpassword
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.loginpassword package io.element.android.features.login.impl.screens.loginpassword
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
@ -24,7 +24,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService

View file

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.loginpassword package io.element.android.features.login.impl.screens.loginpassword
import android.os.Parcelable import android.os.Parcelable
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize

View file

@ -14,10 +14,10 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.loginpassword package io.element.android.features.login.impl.screens.loginpassword
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.login.impl.accountprovider.item.anAccountProvider import io.element.android.features.login.impl.accountprovider.anAccountProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
open class LoginPasswordStateProvider : PreviewParameterProvider<LoginPasswordState> { open class LoginPasswordStateProvider : PreviewParameterProvider<LoginPasswordState> {

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 New Vector Ltd * Copyright (c) 2023 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.loginpassword package io.element.android.features.login.impl.screens.loginpassword
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column

View file

@ -14,11 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.screens.searchaccountprovider
sealed interface ChangeAccountProviderFormEvents { sealed interface SearchAccountProviderEvents {
/** /**
* The user has typed something, expect to get a list of result in the state. * The user has typed something, expect to get a list of result in the state.
*/ */
data class UserInput(val input: String) : ChangeAccountProviderFormEvents data class UserInput(val input: String) : SearchAccountProviderEvents
} }

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.screens.searchaccountprovider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -30,10 +30,10 @@ import io.element.android.features.login.impl.util.openLearnMorePage
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
@ContributesNode(AppScope::class) @ContributesNode(AppScope::class)
class ChangeAccountProviderFormNode @AssistedInject constructor( class SearchAccountProviderNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
private val presenter: ChangeAccountProviderFormPresenter, private val presenter: SearchAccountProviderPresenter,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin { interface Callback : Plugin {
@ -48,7 +48,7 @@ class ChangeAccountProviderFormNode @AssistedInject constructor(
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
val context = LocalContext.current val context = LocalContext.current
ChangeAccountProviderFormView( SearchAccountProviderView(
state = state, state = state,
modifier = modifier, modifier = modifier,
onBackPressed = ::navigateUp, onBackPressed = ::navigateUp,

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.screens.searchaccountprovider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -23,18 +23,20 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
import io.element.android.features.login.impl.changeserver.resolver.HomeserverData
import io.element.android.features.login.impl.changeserver.resolver.HomeserverResolver
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import javax.inject.Inject import javax.inject.Inject
class ChangeAccountProviderFormPresenter @Inject constructor( class SearchAccountProviderPresenter @Inject constructor(
private val homeserverResolver: HomeserverResolver, private val homeserverResolver: HomeserverResolver,
private val changeServerPresenter: ChangeServerPresenter, private val changeServerPresenter: ChangeServerPresenter,
) : Presenter<ChangeAccountProviderFormState> { ) : Presenter<SearchAccountProviderState> {
@Composable @Composable
override fun present(): ChangeAccountProviderFormState { override fun present(): SearchAccountProviderState {
var userInput by rememberSaveable { var userInput by rememberSaveable {
mutableStateOf("") mutableStateOf("")
} }
@ -50,15 +52,15 @@ class ChangeAccountProviderFormPresenter @Inject constructor(
} }
} }
fun handleEvents(event: ChangeAccountProviderFormEvents) { fun handleEvents(event: SearchAccountProviderEvents) {
when (event) { when (event) {
is ChangeAccountProviderFormEvents.UserInput -> { is SearchAccountProviderEvents.UserInput -> {
userInput = event.input userInput = event.input
} }
} }
} }
return ChangeAccountProviderFormState( return SearchAccountProviderState(
userInput = userInput, userInput = userInput,
userInputResult = data, userInputResult = data,
changeServerState = changeServerState, changeServerState = changeServerState,

View file

@ -14,15 +14,16 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.screens.searchaccountprovider
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerState import io.element.android.features.login.impl.changeserver.ChangeServerState
import io.element.android.features.login.impl.changeserver.resolver.HomeserverData
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
// Do not use default value, so no member get forgotten in the presenters. // Do not use default value, so no member get forgotten in the presenters.
data class ChangeAccountProviderFormState( data class SearchAccountProviderState(
val userInput: String, val userInput: String,
val userInputResult: Async<List<HomeserverData>>, val userInputResult: Async<List<HomeserverData>>,
val changeServerState: ChangeServerState, val changeServerState: ChangeServerState,
val eventSink: (ChangeAccountProviderFormEvents) -> Unit val eventSink: (SearchAccountProviderEvents) -> Unit
) )

View file

@ -14,25 +14,26 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.screens.searchaccountprovider
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.login.impl.changeaccountprovider.common.aChangeServerState import io.element.android.features.login.impl.changeserver.aChangeServerState
import io.element.android.features.login.impl.changeserver.resolver.HomeserverData
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
open class ChangeAccountProviderFormStateProvider : PreviewParameterProvider<ChangeAccountProviderFormState> { open class SearchAccountProviderStateProvider : PreviewParameterProvider<SearchAccountProviderState> {
override val values: Sequence<ChangeAccountProviderFormState> override val values: Sequence<SearchAccountProviderState>
get() = sequenceOf( get() = sequenceOf(
aChangeAccountProviderFormState(), aSearchAccountProviderState(),
aChangeAccountProviderFormState(userInputResult = Async.Success(aHomeserverDataList())), aSearchAccountProviderState(userInputResult = Async.Success(aHomeserverDataList())),
// Add other state here // Add other state here
) )
} }
fun aChangeAccountProviderFormState( fun aSearchAccountProviderState(
userInput: String = "", userInput: String = "",
userInputResult: Async<List<HomeserverData>> = Async.Uninitialized, userInputResult: Async<List<HomeserverData>> = Async.Uninitialized,
) = ChangeAccountProviderFormState( ) = SearchAccountProviderState(
userInput = userInput, userInput = userInput,
userInputResult = userInputResult, userInputResult = userInputResult,
changeServerState = aChangeServerState(), changeServerState = aChangeServerState(),

View file

@ -16,7 +16,7 @@
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @file:OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.screens.searchaccountprovider
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -50,10 +50,11 @@ import androidx.compose.ui.tooling.preview.Preview
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.features.login.impl.R import io.element.android.features.login.impl.R
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.accountprovider.item.AccountProviderView import io.element.android.features.login.impl.accountprovider.AccountProviderView
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerEvents import io.element.android.features.login.impl.changeserver.ChangeServerEvents
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerView import io.element.android.features.login.impl.changeserver.ChangeServerView
import io.element.android.features.login.impl.changeserver.resolver.HomeserverData
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
@ -72,8 +73,8 @@ import io.element.android.libraries.testtags.testTag
* https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=611-61435 * https://www.figma.com/file/o9p34zmiuEpZRyvZXJZAYL/FTUE?type=design&node-id=611-61435
*/ */
@Composable @Composable
fun ChangeAccountProviderFormView( fun SearchAccountProviderView(
state: ChangeAccountProviderFormState, state: SearchAccountProviderState,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onBackPressed: () -> Unit = {}, onBackPressed: () -> Unit = {},
onLearnMoreClicked: () -> Unit = {}, onLearnMoreClicked: () -> Unit = {},
@ -123,7 +124,7 @@ fun ChangeAccountProviderFormView(
.testTag(TestTags.changeServerServer), .testTag(TestTags.changeServerServer),
onValueChange = { onValueChange = {
userInputState = it userInputState = it
eventSink(ChangeAccountProviderFormEvents.UserInput(it)) eventSink(SearchAccountProviderEvents.UserInput(it))
}, },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Uri, keyboardType = KeyboardType.Uri,
@ -135,7 +136,7 @@ fun ChangeAccountProviderFormView(
{ {
IconButton(onClick = { IconButton(onClick = {
userInputState = "" userInputState = ""
eventSink(ChangeAccountProviderFormEvents.UserInput("")) eventSink(SearchAccountProviderEvents.UserInput(""))
}) { }) {
Icon( Icon(
imageVector = Icons.Filled.Close, imageVector = Icons.Filled.Close,
@ -202,17 +203,17 @@ private fun HomeserverData.toAccountProvider(): AccountProvider {
@Preview @Preview
@Composable @Composable
fun ChangeAccountProviderFormViewLightPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) = fun SearchAccountProviderViewLightPreview(@PreviewParameter(SearchAccountProviderStateProvider::class) state: SearchAccountProviderState) =
ElementPreviewLight { ContentToPreview(state) } ElementPreviewLight { ContentToPreview(state) }
@Preview @Preview
@Composable @Composable
fun ChangeAccountProviderFormViewDarkPreview(@PreviewParameter(ChangeAccountProviderFormStateProvider::class) state: ChangeAccountProviderFormState) = fun SearchAccountProviderViewDarkPreview(@PreviewParameter(SearchAccountProviderStateProvider::class) state: SearchAccountProviderState) =
ElementPreviewDark { ContentToPreview(state) } ElementPreviewDark { ContentToPreview(state) }
@Composable @Composable
private fun ContentToPreview(state: ChangeAccountProviderFormState) { private fun ContentToPreview(state: SearchAccountProviderState) {
ChangeAccountProviderFormView( SearchAccountProviderView(
state = state, state = state,
) )
} }

View file

@ -16,7 +16,7 @@
package io.element.android.features.login.impl.util package io.element.android.features.login.impl.util
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
object LoginConstants { object LoginConstants {
const val MATRIX_ORG_URL = "matrix.org" const val MATRIX_ORG_URL = "matrix.org"

View file

@ -14,14 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.common package io.element.android.features.login.impl.changeserver
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL

View file

@ -14,10 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.changeserver.resolver.network
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnown
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellknownRequest
class FakeWellknownRequest : WellknownRequest { class FakeWellknownRequest : WellknownRequest {
private var resultMap: Map<String, WellKnown> = emptyMap() private var resultMap: Map<String, WellKnown> = emptyMap()

View file

@ -14,15 +14,15 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider package io.element.android.features.login.impl.screens.changeaccountprovider
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.accountprovider.item.AccountProvider import io.element.android.features.login.impl.accountprovider.AccountProvider
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.accountprovider package io.element.android.features.login.impl.screens.confirmaccountprovider
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.features.login.impl.util.defaultAccountProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_HOMESERVER
@ -30,11 +30,11 @@ import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
class AccountProviderPresenterTest { class ConfirmAccountProviderPresenterTest {
@Test @Test
fun `present - initial test`() = runTest { fun `present - initial test`() = runTest {
val presenter = AccountProviderPresenter( val presenter = ConfirmAccountProviderPresenter(
AccountProviderPresenterParams(isAccountCreation = false), ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
AccountProviderDataSource(), AccountProviderDataSource(),
FakeAuthenticationService(), FakeAuthenticationService(),
) )
@ -52,8 +52,8 @@ class AccountProviderPresenterTest {
@Test @Test
fun `present - continue password login`() = runTest { fun `present - continue password login`() = runTest {
val authServer = FakeAuthenticationService() val authServer = FakeAuthenticationService()
val presenter = AccountProviderPresenter( val presenter = ConfirmAccountProviderPresenter(
AccountProviderPresenterParams(isAccountCreation = false), ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
AccountProviderDataSource(), AccountProviderDataSource(),
authServer, authServer,
) )
@ -62,7 +62,7 @@ class AccountProviderPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(AccountProviderEvents.Continue) initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
val loadingState = awaitItem() val loadingState = awaitItem()
assertThat(loadingState.submitEnabled).isTrue() assertThat(loadingState.submitEnabled).isTrue()
assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java)
@ -76,8 +76,8 @@ class AccountProviderPresenterTest {
@Test @Test
fun `present - continue oidc`() = runTest { fun `present - continue oidc`() = runTest {
val authServer = FakeAuthenticationService() val authServer = FakeAuthenticationService()
val presenter = AccountProviderPresenter( val presenter = ConfirmAccountProviderPresenter(
AccountProviderPresenterParams(isAccountCreation = false), ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
AccountProviderDataSource(), AccountProviderDataSource(),
authServer, authServer,
) )
@ -86,7 +86,7 @@ class AccountProviderPresenterTest {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(AccountProviderEvents.Continue) initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
val loadingState = awaitItem() val loadingState = awaitItem()
assertThat(loadingState.submitEnabled).isTrue() assertThat(loadingState.submitEnabled).isTrue()
assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java) assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java)
@ -100,8 +100,8 @@ class AccountProviderPresenterTest {
@Test @Test
fun `present - submit fails`() = runTest { fun `present - submit fails`() = runTest {
val authServer = FakeAuthenticationService() val authServer = FakeAuthenticationService()
val presenter = AccountProviderPresenter( val presenter = ConfirmAccountProviderPresenter(
AccountProviderPresenterParams(isAccountCreation = false), ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
AccountProviderDataSource(), AccountProviderDataSource(),
authServer, authServer,
) )
@ -110,7 +110,7 @@ class AccountProviderPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
authServer.givenChangeServerError(Throwable()) authServer.givenChangeServerError(Throwable())
initialState.eventSink.invoke(AccountProviderEvents.Continue) initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
skipItems(1) // Loading skipItems(1) // Loading
val failureState = awaitItem() val failureState = awaitItem()
assertThat(failureState.submitEnabled).isFalse() assertThat(failureState.submitEnabled).isFalse()
@ -121,8 +121,8 @@ class AccountProviderPresenterTest {
@Test @Test
fun `present - clear error`() = runTest { fun `present - clear error`() = runTest {
val authenticationService = FakeAuthenticationService() val authenticationService = FakeAuthenticationService()
val presenter = AccountProviderPresenter( val presenter = ConfirmAccountProviderPresenter(
AccountProviderPresenterParams(isAccountCreation = false), ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
AccountProviderDataSource(), AccountProviderDataSource(),
authenticationService, authenticationService,
) )
@ -133,7 +133,7 @@ class AccountProviderPresenterTest {
// Submit will return an error // Submit will return an error
authenticationService.givenChangeServerError(A_THROWABLE) authenticationService.givenChangeServerError(A_THROWABLE)
initialState.eventSink(AccountProviderEvents.Continue) initialState.eventSink(ConfirmAccountProviderEvents.Continue)
skipItems(1) // Loading skipItems(1) // Loading
@ -142,7 +142,7 @@ class AccountProviderPresenterTest {
assertThat(submittedState.loginFlow).isInstanceOf(Async.Failure::class.java) assertThat(submittedState.loginFlow).isInstanceOf(Async.Failure::class.java)
// Assert the error is then cleared // Assert the error is then cleared
submittedState.eventSink(AccountProviderEvents.ClearError) submittedState.eventSink(ConfirmAccountProviderEvents.ClearError)
val clearedState = awaitItem() val clearedState = awaitItem()
assertThat(clearedState.loginFlow).isEqualTo(Async.Uninitialized) assertThat(clearedState.loginFlow).isEqualTo(Async.Uninitialized)
} }

View file

@ -14,13 +14,13 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.loginpassword package io.element.android.features.login.impl.screens.loginpassword
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.features.login.impl.util.defaultAccountProvider
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId

View file

@ -14,17 +14,19 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.login.impl.changeaccountprovider.form package io.element.android.features.login.impl.screens.searchaccountprovider
import app.cash.molecule.RecompositionClock import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow import app.cash.molecule.moleculeFlow
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.login.impl.changeaccountprovider.common.ChangeServerPresenter import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnown import io.element.android.features.login.impl.changeserver.resolver.network.WellKnown
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnownBaseConfig import io.element.android.features.login.impl.changeserver.resolver.network.WellKnownBaseConfig
import io.element.android.features.login.impl.changeaccountprovider.form.network.WellKnownSlidingSyncConfig import io.element.android.features.login.impl.changeserver.resolver.network.WellKnownSlidingSyncConfig
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.changeserver.resolver.HomeserverResolver
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.changeserver.resolver.network.FakeWellknownRequest
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
@ -32,7 +34,7 @@ import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
class ChangeAccountProviderFormPresenterTest { class SearchAccountProviderPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val fakeWellknownRequest = FakeWellknownRequest() val fakeWellknownRequest = FakeWellknownRequest()
@ -40,8 +42,8 @@ class ChangeAccountProviderFormPresenterTest {
FakeAuthenticationService(), FakeAuthenticationService(),
AccountProviderDataSource() AccountProviderDataSource()
) )
val presenter = ChangeAccountProviderFormPresenter( val presenter = SearchAccountProviderPresenter(
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter changeServerPresenter
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
@ -60,15 +62,15 @@ class ChangeAccountProviderFormPresenterTest {
FakeAuthenticationService(), FakeAuthenticationService(),
AccountProviderDataSource() AccountProviderDataSource()
) )
val presenter = ChangeAccountProviderFormPresenter( val presenter = SearchAccountProviderPresenter(
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter changeServerPresenter
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test"))
val withInputState = awaitItem() val withInputState = awaitItem()
assertThat(withInputState.userInput).isEqualTo("test") assertThat(withInputState.userInput).isEqualTo("test")
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)
@ -84,15 +86,15 @@ class ChangeAccountProviderFormPresenterTest {
FakeAuthenticationService(), FakeAuthenticationService(),
AccountProviderDataSource() AccountProviderDataSource()
) )
val presenter = ChangeAccountProviderFormPresenter( val presenter = SearchAccountProviderPresenter(
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter changeServerPresenter
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("https://test.org")) initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("https://test.org"))
val withInputState = awaitItem() val withInputState = awaitItem()
assertThat(withInputState.userInput).isEqualTo("https://test.org") assertThat(withInputState.userInput).isEqualTo("https://test.org")
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)
@ -119,15 +121,15 @@ class ChangeAccountProviderFormPresenterTest {
FakeAuthenticationService(), FakeAuthenticationService(),
AccountProviderDataSource() AccountProviderDataSource()
) )
val presenter = ChangeAccountProviderFormPresenter( val presenter = SearchAccountProviderPresenter(
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter changeServerPresenter
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test"))
val withInputState = awaitItem() val withInputState = awaitItem()
assertThat(withInputState.userInput).isEqualTo("test") assertThat(withInputState.userInput).isEqualTo("test")
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)
@ -154,15 +156,15 @@ class ChangeAccountProviderFormPresenterTest {
FakeAuthenticationService(), FakeAuthenticationService(),
AccountProviderDataSource() AccountProviderDataSource()
) )
val presenter = ChangeAccountProviderFormPresenter( val presenter = SearchAccountProviderPresenter(
DefaultHomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest), HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
changeServerPresenter changeServerPresenter
) )
moleculeFlow(RecompositionClock.Immediate) { moleculeFlow(RecompositionClock.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(ChangeAccountProviderFormEvents.UserInput("test")) initialState.eventSink.invoke(SearchAccountProviderEvents.UserInput("test"))
val withInputState = awaitItem() val withInputState = awaitItem()
assertThat(withInputState.userInput).isEqualTo("test") assertThat(withInputState.userInput).isEqualTo("test")
assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized) assertThat(initialState.userInputResult).isEqualTo(Async.Uninitialized)

View file

@ -20,9 +20,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.element.android.features.login.impl.datasource.AccountProviderDataSource import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
import io.element.android.features.login.impl.loginpassword.LoginPasswordPresenter import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordPresenter
import io.element.android.features.login.impl.loginpassword.LoginPasswordView import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordView
import io.element.android.features.login.impl.util.defaultAccountProvider import io.element.android.features.login.impl.util.defaultAccountProvider
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService